ap 在 Haskell 中如何以及为何被定义为 liftM2 id
在试图更好地理解 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)
。我在这里缺少什么?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
id
中的类型变量a
可以以任何类型实例化,在本例中,该类型为a -> b.
。所以我们在
(a -> b) -> 处实例化
。现在,来自id
(a -> b)liftM2
的类型变量a1
正在(a -> b)
处实例化,a2
正在被实例化在a
处实例化,r
在b
处实例化。将它们放在一起,
liftM2
在((a -> b) -> (a -> b)) -> 处实例化。 m(a→b)→妈-> m b
和 liftM2 id :: m (a -> b) ->妈-> m b。The type variable
a
fromid
can be instantiated at any type, and in this case that type isa -> b
.So we are instantiating
id
at(a -> b) -> (a -> b)
. Now the type variablea1
fromliftM2
is being instantiated at(a -> b)
,a2
is being instantiated ata
, andr
is being instantiated atb
.Putting it all together,
liftM2
is instantiated at((a -> b) -> (a -> b)) -> m (a -> b) -> m a -> m b
, andliftM2 id :: m (a -> b) -> m a -> m b
.最佳答案绝对是正确的,并且仅从类型即可快速有效地工作。一旦你擅长 Haskell(免责声明:我不擅长),那么这是一种比摆脱函数定义更有效的理解方法。
但由于我最近在处理 ap 来解决这个问题。 >Monad 挑战,我决定分享我的经验,因为它可能提供一些额外的直觉。
首先,正如 Monad 挑战所要求的那样,我将使用名称
bind
来引用主要 Monad 运算符>>=
。我认为这很有帮助。如果我们定义自己的
liftM2
版本,我们可以这样做:我们希望使用它来创建
ap
来帮助我们。让我们暂时不谈函数f
,只要考虑一下它如何作为ap
工作,假设我们为f
选择了正确的函数。假设我们要传递一个函数值 Monad 作为上面的第一部分,即
ma
部分。它可能类似于Just (+3)
或[(+3), (*2)]
—— Monad 上下文中的一些“函数”。我们提供一个参数值 Monad 作为第二部分,即
mb
部分,例如Just 5
或[6,7,8]
-- Monad 上下文中的一些“值”可以作为ma
中函数的参数。然后我们在
bind
之后的 lambda 函数中,我们知道a
将是someFunction
和b
将是someArgument
——因为这就是bind
的作用:它模拟从 Monad 上下文中提取一个值,并对该 Monad 特有的任何特殊处理进行取模。所以最后一行实际上变成了
现在让我们退后一步,记住我们创建
ap
的目标是在 Monad 上下文内的someArgument
上调用someFunction
。因此,无论我们使用return
产生什么结果,它都需要是函数应用someFunction someArgument
的结果。那么我们怎样才能使两个表达式相等
呢?如果我们让
x = (someFunction someArgument)
那么我们正在寻找一个函数f
这样我们就知道
f
需要是id
。回到开头,这意味着我们正在寻找
liftM2 id
。基本上
liftM2 id ma mb
表示我要做m (id ab)
所以如果a
是一个可以在上操作的函数>b
,那么id
将“不理会它们”并让a
对b
执行其操作,同时返回结果在 Monad 上下文内部。这就像我们强迫
liftM2
产生旁观者偏见。为了实现这一点,
a
必须具有从“TypeOfb”到“SomeReturnType”的函数类型,或者TypeOfb -> SomeReturnType
,因为b
是a
的预期参数。当然,b
必须有TypeOfb
。如果您允许我滥用一种符号,那么我们就任意使用符号“a”代表“TypeOfb”,使用符号“b”代表“SomeReturnType”:
然后是类型
ap
的签名是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:We want to create
ap
using this to help us. Let's leave the functionf
alone for a moment and just think about how this could work asap
assuming we picked the right function forf
.Suppose that we were to pass a function-valued Monad as the first part above, the
ma
part. It could be something likeJust (+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 asJust 5
or[6,7,8]
-- some "value" living in a Monad context which can serve as the argument for the function living insidema
.Then we'd have
and inside the lambda functions following
bind
, we know thata
will besomeFunction
andb
will besomeArgument
-- for that is whatbind
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
Now let's step back and remember that our goal in creating
ap
is to callsomeFunction
uponsomeArgument
inside of the Monad context. So whatever our use ofreturn
yields, it needs to be the result of the function applicationsomeFunction someArgument
.So how can we make the two expressions be equal
Well, if we let
x = (someFunction someArgument)
then we're looking for a functionf
such thatand so we know that
f
needs to beid
.Going back to the start, this means we're looking for
liftM2 id
.Basically
liftM2 id ma mb
says I'm going to dom (id a b)
so ifa
is a function that can operate onb
, thenid
will "just leave them alone" and leta
do its thing tob
, 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", orTypeOfb -> SomeReturnType
, becauseb
isa
's expected argument. And of courseb
has to haveTypeOfb
.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":
Then the type signature for
ap
would be