如何以及为什么在Haskell中将ap定义为liftM2 id

时间:2011-03-18 23:40:26

标签: haskell applicative

在尝试更好地理解Applicative时,我查看了< *>的定义,该定义往往被定义为ap,后者又被定义为:

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

查看liftM2和id的类型签名,即:

liftM2  :: (Monad m) => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
id                      :: a -> a

我无法理解如何通过传递id,类型签名的相关部分似乎从(a1 -> a2 -> r) -> m a1转换为m (a -> b)。我在这里缺少什么?

2 个答案:

答案 0 :(得分:17)

来自a的类型变量id可以在任何类型实例化,在这种情况下,该类型为a -> b

所以我们在id实例化(a -> b) -> (a -> b)。现在,来自a1的类型变量liftM2正在(a -> b)实例化,a2正在a实例化,而r正在实例化b

总而言之,liftM2已在((a -> b) -> (a -> b)) -> m (a -> b) -> m a -> m bliftM2 id :: m (a -> b) -> m a -> m b实例化。

答案 1 :(得分:2)

最佳答案肯定是正确的,并且仅从类型中快速有效地工作。一旦你擅长Haskell(免责声明:我不是),那么这是理解这一点的一种更有效的方式,而不是通过功能定义。

但是,由于我最近在The Monad Challenges工作时不得不与ap完全解决这个问题,我决定分享我的经验,因为它可能提供一些额外的直觉。

首先,正如Monad Challenges所要求的那样,我将使用名称bind来引用主Monad运算符>>=。我认为这有很大的帮助。

如果我们定义自己的liftM2版本,我们可以这样做:

liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c
liftM2 f ma mb = 
    ma `bind` \a -> 
    mb `bind` \b -> 
    return $ f a b

我们希望使用此功能创建ap来帮助我们。让我们暂时离开函数f,假设我们为ap选择了正确的函数,只考虑它如何作为f

假设我们要传递一个函数值Monad作为上面的第一部分ma部分。它可能类似于Just (+3)[(+3), (*2)] - 一些生活在Monad环境中的“函数”。

我们提供一个参数值Monad作为第二部分,mb部分,例如Just 5[6,7,8] - 一些生活在Monad环境中的“价值”作为生活在ma内的函数的参数。

然后我们有

liftM2 f (m someFunction) (m someArgument) = 
    (m someFunction) `bind` \a ->
    (m someArgument) `bind` \b ->
    return $ (f a b)

并且在bind之后的lambda函数中,我们知道a将是someFunctionb将是someArgument - 因为这就是{ {1}}:它模拟Monad上下文中的值的提取,以模拟Monad特有的任何特殊处理。

所以最后一行确实变成了

bind

现在让我们退后一步,记住我们创建return $ f someFunction someArgument 的目标是在Monad上下文中的ap上调用someFunction。因此无论我们使用someArgument产生什么,它都需要是函数应用程序return的结果。

那么我们怎样才能使两个表达式相等

someFunction someArgument

好吧,如果我们让f someFunction someArgument ==? someFunction someArgument ,那么我们正在寻找一个函数x = (someFunction someArgument),以便

f

因此我们知道f x = x 需要f

回到开头,这意味着我们正在寻找id

基本上liftM2 id说我要liftM2 id ma mb,所以如果m (id a b)是一个可以在a上运行的函数,那么b就会“让他们独自“让ida做事,同时将结果返回到Monad上下文中。

就像我们强迫b有旁观者偏见。

为了解决这个问题,liftM2必须有一个从“TypeOfb”到“SomeReturnType”或a的函数类型,因为TypeOfb -> SomeReturnType是{ {1}}预期的论点。当然b必须有a

如果你允许我滥用表示法,那么任意让我们只使用符号“a”代表“TypeOfb”而符号“b”代表“SomeReturnType”:

b

然后TypeOfb的类型签名将是

`b` --> "a" is its type
`a` --> "a -> b" is its type