假设在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
(这个问题与...基本相同 Grouping functions in Haskell 但在那里,我使用了一些让人分心的不幸言辞。)
答案 0 :(得分:6)
在Haskell中,所有函数都接受一个输入参数。但有时候,应用函数的返回值是一个新函数。然后,作为第一步,您可以通过在函数f
和g
的返回值周围加上括号来使其更明确:
f :: Par -> (a -> b)
g :: Par -> (b -> c)
函数也是类型,因此我们可以随意决定将a -> b
别名变为φ
( phi 而不是 f )和{{ 1}}到b -> c
( gamma 而不是 g )。 (是的,当你用完字母时,你会找到希腊字母!)
这意味着您可以将您的功能视为具有类型
γ
这些都是所谓的 reader monad 的自动实例,它也是一个(应用)仿函数。特别是,f :: Par -> φ
g :: Par -> γ
,或者,如果有帮助,(->) Par
,则是Par ->
个实例。这意味着您可以使用Applicative
和pure
。
作为第一次尝试,您可以编写类似
的内容<*>
为了简单理解该组合的工作原理。该表达式的类型为pure (\x y -> (x, y)) <*> f <*> g
,可以这么说。该lambda表达式只需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
然后可以将显式的lambda表达式简化为:
h = pure (\y x -> y . x) <*> g <*> f
最后,您可以使用中缀h = pure (.) <*> g <*> f
运算符代替编写pure (.) <*>
:
<$>
此函数的类型为h = (.) <$> g <*> f
。
答案 1 :(得分:5)
如果您可以略微调整签名,则您已发现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)
。)
答案 2 :(得分:3)
你正在做h par = f par . g par
很多,par
的东西开始变得杂乱无章。
你不能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
。这个算子可能是之前发明的。
顺便说一句,部分应用的函数是instances of 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
。monad实例使par
隐式。) < / p>
如果我们要参数化这个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 (.)
已经很短了。
答案 3 :(得分:1)
这可以使用隐式参数(不是纯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