在Haskell中理解一个无点函数中的`ap`

时间:2015-10-31 17:46:50

标签: haskell functional-programming pointfree

我能够理解Haskell中无点函数的基础知识:

addOne x = 1 + x

当我们在等式的两边看到x时,我们简化它:

addOne = (+ 1)

令人难以置信的是,在不同部分使用相同参数两次的函数可以无点编写!

让我以average函数为基本示例编写为:

average xs = realToFrac (sum xs) / genericLength xs

似乎无法简化xs,但http://pointfree.io/出现了:

average = ap ((/) . realToFrac . sum) genericLength

有效。

据我了解,这表明average与在两个函数上调用ap相同,(/) . realToFrac . sumgenericLength

的构成

不幸的是,ap函数对我来说没有任何意义,文档http://hackage.haskell.org/package/base-4.8.1.0/docs/Control-Monad.html#v:ap表示:

ap :: Monad m => m (a -> b) -> m a -> m b

In many situations, the liftM operations can be replaced by uses of ap,
which promotes function application.

      return f `ap` x1 `ap` ... `ap` xn

is equivalent to

      liftMn f x1 x2 ... xn

但写作:

let average = liftM2 ((/) . realToFrac . sum) genericLength

不起作用,(提供一个非常长的类型错误消息,请问我会包括它),所以我不明白文档试图说什么。

表达式ap ((/) . realToFrac . sum) genericLength如何工作?你能用简单的术语解释ap吗?

2 个答案:

答案 0 :(得分:9)

任何lambda术语都可以重写为只使用一组合适的combinators且没有lambda抽象的等效术语。此过程称为abstraciton elimination。在此过程中,您希望从内到外删除lambda抽象。所以,只需一步,λx.M M已经没有lambda抽象,你想要摆脱x

  • 如果Mx,则将λx.x替换为idid通常在组合逻辑中用I表示。)< / LI>
  • 如果M不包含x,则将该字词替换为const Mconst通常在组合逻辑中用K表示。)< / LI>
  • 如果MPQ,即术语为λx.PQ,您希望在功能应用程序的两个部分内“推送”x,以便你可以递归地处理这两个部分。这是通过使用定义为S的{​​{1}}组合子来完成的,也就是说,它需要两个函数并将λfgx.(fx)(gx)传递给它们,并将结果一起应用。您可以轻松验证x是否等同于λx.PQ,我们可以递归处理这两个子项。

    如其他答案中所述,S(λx.P)(λx.Q)组合子在Haskell中可用作S(或ap)专门用于读者monad。

读者monad的外观并非偶然:当解决用等效函数替换<*>的任务时,基本上将λx.M提升到读者monad M :: a(实际上是读者)应用部分就足够了,其中r -> ar的类型。如果我们修改上述流程:

  • 实际与读者monad相关联的唯一情况是xM时。然后我们将x“提升”到x,以摆脱变量。下面的其他案例只是将表达式提升到应用程序仿函数的机械应用程序:
  • 另一个案例id,其中λx.M不包含M,它只是将x提升为读者适用者,即M。实际上,对于pure M(->) r相当于pure
  • 在最后一种情况下,const是将函数应用程序解除为monad / applicative。这正是我们所做的:我们将两个部分<*> :: f (a -> b) -> f a -> f bP提升到阅读器应用程序,然后使用Q将它们绑定在一起。

通过添加更多的组合器可以进一步改善该过程,这使得所得的术语更短。通常,combinators B and C are used,在Haskell中对应于函数<*>(.)。同样,flip只适用于(.) / fmap。 (我不知道有这样一个用于表达<$>的内置函数,但它被视为适用于读者的flip的特化。)

前段时间我写了一篇关于此的短文:The Monad Reader Issue 17,读者Monad和抽象消除。

答案 1 :(得分:6)

当monad m(->) a时,就像您的情况一样,您可以按如下方式定义ap

ap f g = \x -> f x (g x)

我们可以看到这确实&#34;工作&#34;在你的无点示例中。

average = ap ((/) . realToFrac . sum) genericLength
average = \x -> ((/) . realToFrac . sum) x (genericLength x)
average = \x -> (/) (realToFrac (sum x)) (genericLength x)
average = \x -> realToFrac (sum x) / genericLength x

我们也可以从一般法律中获得ap

ap f g = do ff <- f ; gg <- g ; return (ff gg)

即,贬低do - 符号

ap f g = f >>= \ff -> g >>= \gg -> return (ff gg)

如果我们替换monad方法的定义

m >>= f = \x -> f (m x) x
return x = \_ -> x

我们返回ap的先前定义(针对我们的特定monad (->) a)。事实上:

app f g 
= f >>= \ff -> g >>= \gg -> return (ff gg)
= f >>= \ff -> g >>= \gg -> \_ -> ff gg
= f >>= \ff -> g >>= \gg _ -> ff gg
= f >>= \ff -> \x -> (\gg _ -> ff gg) (g x) x
= f >>= \ff -> \x -> (\_ -> ff (g x)) x
= f >>= \ff -> \x -> ff (g x)
= f >>= \ff x -> ff (g x)
= \y -> (\ff x -> ff (g x)) (f y) y
= \y -> (\x -> f y (g x)) y
= \y -> f y (g y)