在Haskell中,monad是根据函数return和bind定义的,其中return有类型a -> m a
,bind有类型m a -> (a -> m b) -> m b
.之前已经指出monad也可以用return和join来定义,其中join是一个带有类型的函数m (m a) -> m a
.绑定可以用连接来定义,但是反过来可能吗?可以在绑定方面加入定义吗?
没有加入,我不知道如果我以某种方式获得"两次包裹"的monadic值,我会做什么,m (m a)
- 没有任何仿函数或monad操作"删除任何层",可以这么说.如果这是不可能的,为什么Haskell和许多其他monad实现在绑定方面定义它们?它似乎没有基于连接的定义有用.
chi*_*chi 10
有可能的:
join :: Monad m => m (m a) -> m a
join m = (m >>= id)
Run Code Online (Sandbox Code Playgroud)
请注意棘手的实例化>>=
:
(>>=) :: m b -> (b -> m c) -> m c
-- choosing b ~ m a , c ~ a
(>>=) :: m (m a) -> (m a -> m a) -> m a
Run Code Online (Sandbox Code Playgroud)
所以我们可以正确选择id
第二个参数.
小智 7
虽然这个问题已经得到了充分的回答,但我认为对于任何 Haskell 新手来说,以下几点值得注意。
在 Haskell 中学习 monad 时,您首先看到的一件事是列表的定义:
instance Monad [] where
return x = [x]
xs >>= f = concatMap f xs
Run Code Online (Sandbox Code Playgroud)
在这里,我们看到绑定列表的功能等同于concatMap
,只是参数翻转了。这在检查类型时很有意义:
concatMap :: (a -> [b]) -> [a] -> [b]
bind :: Monad m => (a -> m b ) -> m a -> m b -- (>>=) flips the arguments
Run Code Online (Sandbox Code Playgroud)
join
for 列表的定义等价于
concat :: [[a]] -> [a]
.
函数的名称可能使它有点明显,但concat
可以concatMap
通过保持内部列表原样来恢复,以便取消“映射”部分concatMap
:
concatMap id xs
= concat (map id xs)
= concat ( id xs)
= concat xs -- or simply 'concat = concatMap id'
Run Code Online (Sandbox Code Playgroud)
相同的属性适用于一般的 monad:
join x = x >>= id -- or 'join = bind id'
Run Code Online (Sandbox Code Playgroud)
这真的来自这样一个事实
bind f m = join (fmap f m)
Run Code Online (Sandbox Code Playgroud)
以便
bind id m = join (fmap id m) -- bind id = join . fmap id
= join ( id m) -- = join . id
= join m -- = join
Run Code Online (Sandbox Code Playgroud)
因为所有的 monad 首先都是 Functors,而且是 Functor 定律fmap id === id
。
Bind ( >>=
) 实际上确实“删除了一个层”:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Run Code Online (Sandbox Code Playgroud)
直观地说,它“a
从“中获取一些s m a
,然后将其提供给a -> m b
函数,然后m b
从结果中生成一个。
人们通常说它需要函数参数将其输出再次包装在 中m
,但实际上并非如此。它要求函数的输出是包裹在 中的东西m
,但包裹来自哪里并不重要。
在实施的情况下,join
我们从什么开始的“双包”: m (m a)
。我们可以将其插入到 bind 的签名中,并立即找出在绑定“双重包装”值时我们可以使用的函数类型:
m (m a) -> (m a -> m b) -> m b
Run Code Online (Sandbox Code Playgroud)
现在与 bind 一起使用的函数将接收一个已经包含在m
. 所以我们不必“重新包装”任何东西;如果我们不加修改地返回它,它就已经是输出的正确类型。这实际上是“去除了一层包装”——这适用于除最后一层之外的任何层。
所以这告诉我们我们只需要绑定id
:
join = (>>= id)
Run Code Online (Sandbox Code Playgroud)