[英]Partially apply several functions in Haskell
假设,在Haskell中,我有一堆函数都依赖于相同的参数类型:
f :: Par -> a -> b
g :: Par -> b -> c
由于我正在编写更多依赖于此参数类型的函数,我可以执行类似的操作
h :: Par -> a -> c
h par = myg . myf
where myf = f par
myg = g par
但是我不得不把这些写where
。 问题是:这可以避免吗?
[编辑:我试图提供一个最小的例子来说明问题,但显然这个例子太小而无法说明我想要的东西。 在实际问题中,h当然不仅仅是f和g的组成。 所以这是一些实际的代码:
有功能
apply :: ChamberLattice -> ChLatword -> ChLatWord
reduce :: ChamberLattice -> ChLatWord -> ChLatWord
我正在定义一个函数
chaseTurn :: ChamberLattice -> Turn -> Parity -> ChLatWord -> ChLatWord
chaseTurn cl Straight _ xs = xs
chaseTurn cl t parity xs = if ((turn parity xs) == t)
then case myApply xs of
(y1:y2:ys) -> (y1:y2:(myChaseTurn t parity ys))
ys -> ys
else myReduce xs
where myApply = apply cl
myChaseTurn = chaseTurn cl
myReduce = reduce cl
]
(这个问题与Haskell中的分组函数基本相同,但在那里我使用了一些让人分心的不幸词。)
在Haskell中,所有函数都接受一个输入参数。 但有时候,应用函数的返回值是一个新函数。 作为第一步,您可以通过在函数f
和g
的返回值周围放置括号来使其更明确:
f :: Par -> (a -> b)
g :: Par -> (b -> c)
函数也是类型,因此我们可以任意决定别名a -> b
到φ
( phi而不是f )和b -> c
到γ
( gamma而不是g )。 (是的,当你用完字母时,你会找到希腊字母!)
这意味着您可以将您的功能视为具有类型
f :: Par -> φ
g :: Par -> γ
这些都是所谓的阅读器monad的自动实例,它也是一个(应用)函子。 特别是, (->) Par
,或者,如果它有帮助, Par ->
,是一个Applicative
实例。 这意味着您可以使用pure
和<*>
。
作为第一次尝试,你可以写一些像
pure (\x y -> (x, y)) <*> f <*> g
为了简单地理解该组合是如何工作的。 该表达式具有Par -> (φ, γ)
,可以这么说。 即λ表达式简单地取x
从f
“容器”,和y
从g
“容器”,并且将它们组合在一元组。 元组的第一个元素的类型为φ
,第二个元素的类型为γ
。
插入φ
和γ
的定义,得到Par -> (a -> b, b -> c)
。
您需要组合这些函数,而不是将返回值作为函数元组。 您可以使用函数组合运算符.
为了那个原因:
h = pure (\x y -> y . x) <*> f <*> g
请注意,函数从右到左组成,因此首先是x
( a -> b
),然后是y
( b -> c
)。
但是,您可以翻转f
和g
:
h = pure (\y x -> y . x) <*> g <*> f
然后可以将显式lambda表达式简化为:
h = pure (.) <*> g <*> f
最后,您可以使用infix <$>
运算符代替编写pure (.) <*>
:
h = (.) <$> g <*> f
此函数的类型为Par -> a -> c
。
您已经发现了Reader
monad的用例,如果您可以稍微调整您的签名。 如果你有
f :: a -> Par -> b
g :: b -> Par -> c
你可以将它们重新定义为
import Control.Monad.Trans.Reader
f :: a -> Reader Par b
g :: b -> Reader Par c
然后,您可以使用普通的Kleisli合成运算符定义h
。
import Control.Monad
h :: a -> Reader Par c
h = f >=> g
(即使不改变签名,我认为你可以写h = flip (flip f >=> flip g)
。)
你正在做h par = f par . g par
h par = f par . g par
很多,和par
的东西开始混乱。
你不能做h = f . g
h = f . g
,因为par
参数也必须传递。
所以你想出了一个高性能的合成运算符,它将为你做到这一点:
-- (.) :: (b -> c) -> (a -> b) -> a -> c
(§) :: (par -> b -> c) -> (par -> a -> b) -> par -> a -> c
(§) f g par = f par . g par
现在你可以做h = f § g
。 这个操作员可能是之前发明的。
顺便提一下,部分应用的函数是Monad的实例 。 这意味着您可以:
(§) f g par = (do { fpar <- f; gpar <- g; return (fpar . gpar) }) par
要不就:
(§) f g = do { fpar <- f; gpar <- g; return (fpar . gpar) }
(在此, fpar
是f
到隐式par
已被应用。该单子实例使得par
隐式的。)
如果我们要参数化这个do-block:
(§) f g = ( \f m1 m2 -> do { x1 <- m1; x2 <- m2; return (f x1 x2) } ) (.) f g
并且eta-减少参数:
(§) = ( \f m1 m2 -> do { x1 <- m1; x2 <- m2; return (f x1 x2) } ) (.)
看看Hoogle上看起来像这样做的东西,你会发现liftM2
:
(§) = liftM2 (.)
在这一点上,我们并不需要给它一个特殊的名称,因为liftM2 (.)
已经很短了。
这可以使用隐式参数(不是纯Haskell而是ghc语言扩展,请参阅https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#implicit-parameters )来完成。
上面的代码就变成了
f :: (?p :: Par) => a -> b
g :: (?p :: Par) => b -> c
h :: (?p :: Par) => a -> c
h = g . f
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.