繁体   English   中英

在Haskell中部分应用几个函数

[英]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中,所有函数都接受一个输入参数。 但有时候,应用函数的返回值是一个新函数。 作为第一步,您可以通过在函数fg的返回值周围放置括号来使其更明确:

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 -> (φ, γ) ,可以这么说。 即λ表达式简单地取xf “容器”,和yg “容器”,并且将它们组合在一元组。 元组的第一个元素的类型为φ ,第二个元素的类型为γ

插入φγ的定义,得到Par -> (a -> b, b -> c)

您需要组合这些函数,而不是将返回值作为函数元组。 您可以使用函数组合运算符. 为了那个原因:

h = pure (\x y -> y . x) <*> f <*> g

请注意,函数从右到左组成,因此首先是xa -> b ),然后是yb -> c )。

但是,您可以翻转fg

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) }

(在此, fparf到隐式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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM