ap 在 Haskell 中如何以及为何被定义为 liftM2 id

发布于 2024-10-25 05:53:38 字数 498 浏览 1 评论 0 原文

在试图更好地理解 Applicative 的同时,我查看了 <*> 的定义,它往往被定义为 ap,而 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)。我在这里缺少什么?

Whilst trying to better understand Applicative, I looked at the definition of <*>, which tends to be defined as ap, which in turn is defined as:

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

Looking at the type signatures for liftM2 and id, namely:

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

I fail to understand how just by passing in id, the relevant part of the type signature seems to transform from (a1 -> a2 -> r) -> m a1 to m (a -> b). What am I missing here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

递刀给你 2024-11-01 05:53:39

id 中的类型变量 a 可以以任何类型实例化,在本例中,该类型为 a -> b.

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

将它们放在一起,liftM2((a -> b) -> (a -> b)) -> 处实例化。 m(a→b)→妈-> m b 和 liftM2 id :: m (a -> b) ->妈-> m b。

The type variable a from id can be instantiated at any type, and in this case that type is a -> b.

So we are instantiating id at (a -> b) -> (a -> b). Now the type variable a1 from liftM2 is being instantiated at (a -> b), a2 is being instantiated at a, and r is being instantiated at b.

Putting it all together, liftM2 is instantiated at ((a -> b) -> (a -> b)) -> m (a -> b) -> m a -> m b, and liftM2 id :: m (a -> b) -> m a -> m b.

浅笑轻吟梦一曲 2024-11-01 05:53:39

最佳答案绝对是正确的,并且仅从类型即可快速有效地工作。一旦你擅长 Haskell(免责声明:我不擅长),那么这是一种比摆脱函数定义更有效的理解方法。

但由于我最近在处理 ap 来解决这个问题。 >Monad 挑战,我决定分享我的经验,因为它可能提供一些额外的直觉。

首先,正如 Monad 挑战所要求的那样,我将使用名称 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 ——因为这就是 bind 的作用:它模拟从 Monad 上下文中提取一个值,并对该 Monad 特有的任何特殊处理进行取模。

所以最后一行实际上变成了

return $ f someFunction someArgument

现在让我们退后一步,记住我们创建 ap 的目标是在 Monad 上下文内的 someArgument 上调用 someFunction 。因此,无论我们使用 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 ab) 所以如果 a 是一个可以在 上操作的函数>b,那么 id 将“不理会它们”并让 ab 执行其操作,同时返回结果在 Monad 上下文内部。

这就像我们强迫 liftM2 产生旁观者偏见。

为了实现这一点,a 必须具有从“TypeOfb”到“SomeReturnType”的函数类型,或者 TypeOfb -> SomeReturnType,因为 ba 的预期参数。当然,b 必须有 TypeOfb

如果您允许我滥用一种符号,那么我们就任意使用符号“a”代表“TypeOfb”,使用符号“b”代表“SomeReturnType”:

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

然后是类型ap 的签名是

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

The top answer is definitely correct and works quickly and efficiently from the types alone. Once you are good at Haskell (disclaimer: I am not) then this is a much more efficient way to understand this than to slough through the function definitions.

But since I recently had to struggle through exactly this issue with ap while I was working on The Monad Challenges, I decided to share my experience because it may provide some extra intuition.

First, just as The Monad Challenges asks, I will use the name bind to refer to the primary Monad operator >>=. I think this helps a lot.

If we define our own version of liftM2 we can do this:

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

We want to create ap using this to help us. Let's leave the function f alone for a moment and just think about how this could work as ap assuming we picked the right function for f.

Suppose that we were to pass a function-valued Monad as the first part above, the ma part. It could be something like Just (+3) or [(+3), (*2)] -- some "function" living inside a Monad context.

And we supply an argument-valued Monad as the second part, the mb part, such as Just 5 or [6,7,8] -- some "value" living in a Monad context which can serve as the argument for the function living inside ma.

Then we'd have

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

and inside the lambda functions following bind, we know that a will be someFunction and b will be someArgument -- for that is what bind does: it simulates the extraction of a value out of the Monad context modulo any special processing that is unique to that Monad.

So that final line really becomes

return $ f someFunction someArgument

Now let's step back and remember that our goal in creating ap is to call someFunction upon someArgument inside of the Monad context. So whatever our use of return yields, it needs to be the result of the function application someFunction someArgument.

So how can we make the two expressions be equal

f someFunction someArgument ==? someFunction someArgument

Well, if we let x = (someFunction someArgument) then we're looking for a function f such that

f x = x

and so we know that f needs to be id.

Going back to the start, this means we're looking for liftM2 id.

Basically liftM2 id ma mb says I'm going to do m (id a b) so if a is a function that can operate on b, then id will "just leave them alone" and let a do its thing to b, while returning the result inside of the Monad context.

It's like we've forced liftM2 to have bystander bias.

And in order for that to work out, a will have to have a function type that goes from "TypeOfb" to "SomeReturnType", or TypeOfb -> SomeReturnType, because b is a's expected argument. And of course b has to have TypeOfb.

If you'll permit me one abuse of notation, then arbitrarily let's just use the symbol "a" to stand for "TypeOfb" and the symbol "b" to stand for "SomeReturnType":

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

Then the type signature for ap would be

ap :: Monad m => m (TypeOfB -> SomeReturnType) -> m TypeOfB -> m SomeReturnType
==>
ap :: Monad m => m (a -> b) -> m a -> m b  
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文