Function 在Haskell中部分应用几个函数
假设在Haskell中,我有一组函数都依赖于相同的参数类型:Function 在Haskell中部分应用几个函数,function,haskell,function-composition,Function,Haskell,Function Composition,假设在Haskell中,我有一组函数都依赖于相同的参数类型: f :: Par -> a -> b g :: Par -> b -> c 当我编写更多仍然依赖于此参数类型的函数时,我可以执行以下操作 h :: Par -> a -> c h par = myg . myf where myf = f par myg = g par pure (\x y -> (x, y)) <*> f <*> g
f :: Par -> a -> b
g :: Par -> b -> c
当我编写更多仍然依赖于此参数类型的函数时,我可以执行以下操作
h :: Par -> a -> c
h par = myg . myf
where myf = f par
myg = g par
pure (\x y -> (x, y)) <*> f <*> g
然而,我不得不一直写这些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中,所有函数都接受一个输入参数。但有时,应用函数的返回值是一个新函数。然后,作为第一步,您可以在函数的返回值
f
和g
周围放上括号,使其更加明确:
f :: Par -> (a -> b)
g :: Par -> (b -> c)
函数也是类型,因此我们可以任意决定将a->b
别名为φ
(φ而不是f)和b->c
别名为γ(γ而不是g)。(是的,当你的字母用完时,你会去拿希腊字母表!)
这意味着您可以将函数视为具有
f :: Par -> φ
g :: Par -> γ
这两个都是所谓的reader monad的自动实例,它也是一个(应用程序)函子。特别是,(>)Par
,或者如果有帮助的话,Par->
,是一个应用程序的
实例。这意味着您可以使用pure
和
作为第一次尝试,您可以编写如下内容
h :: Par -> a -> c
h par = myg . myf
where myf = f par
myg = g par
pure (\x y -> (x, y)) <*> f <*> g
请注意,函数是从右到左组合的,因此x
(a->b
)排在第一位,然后是y
(b->c
)
但是,您可以翻转f
和g
左右:
h = pure (\y x -> y . x) <*> g <*> f
h=pure(\yx->y.x)gf
该显式lambda表达式可简化为:
h = pure (.) <*> g <*> f
h=纯(.)gf
最后,您可以使用中缀
操作符,而不是编写纯(.)
:
h = (.) <$> g <*> f
h=(.)gf
此函数具有类型<代码> PAL> A>C.<代码> .< /P> < p>您正在执行<代码> 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
。这个操作符可能是以前发明的
顺便提一下,部分应用的函数是。这意味着您可以:
(§) f g par = (do { fpar <- f; gpar <- g; return (fpar . gpar) }) par
并减少参数:
(§) = ( \f m1 m2 -> do { x1 <- m1; x2 <- m2; return (f x1 x2) } ) (.)
在这一点上,我们真的不需要给它一个特殊的名称,因为
liftM2(.)
已经很短了。如果您可以稍微调整您的签名,您已经发现了阅读器的用例。如果你有
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)
)这可以使用隐式参数来完成(隐式参数不是纯粹的Haskell,而是ghc语言扩展,请参阅)
上面的代码就变成了
f :: (?p :: Par) => a -> b
g :: (?p :: Par) => b -> c
h :: (?p :: Par) => a -> c
h = g . f
我不明白你为什么需要<代码>这里的子句,你可以把它写为<代码> h PAR=F PAR。g par
,甚至更好的是h=liftM2(.)f g
@WillemVanOnsemh=(.)f g
?@MarkSeemann的答案是当我想到“我得到了这个东西,我需要传递给很多函数,然后结合它们的结果”时,我经常会想到的模式。有点像叉形连接。例如,(&&&)p1p2
是我经常使用的一个。@StefanWitzel:另一个选择是,如果你有工作代码,并且你正在寻找更好的抽象,你可以在上提交一段代码。术语说明:这是部分应用程序,而不是部分评估。部分求值是一种优化技术,它在编译时在一定程度上运行代码,以减少运行时所需的工作量。这和超级编译有关。@WillNess谢谢:)只是在寻找下一个困惑的新手。:)非常感谢。直到我重建了“->”的单子结构,我才完全理解你的答案。它帮助我把类型“p->a”想象成“由p参数化的a”。
import Control.Monad
h :: a -> Reader Par c
h = f >=> g
f :: (?p :: Par) => a -> b
g :: (?p :: Par) => b -> c
h :: (?p :: Par) => a -> c
h = g . f