在Haskell中,monad是根据函数return和bind定义的,其中return的类型为a -> m a
,bind的类型为m a -> (a -> m b) -> m b
。在monads can also be defined in terms of return and join之前已经指出过,其中join是一个类型为m (m a) -> m a
的函数。绑定可以用连接来定义,但是反过来可能吗?可以用bind来定义join吗?
没有加入,我不知道如果我以某种方式获得了“两次包裹”的monadic值,m (m a)
- 我没有做任何仿函数或monad操作“删除任何图层”,所以说话。如果这是不可能的,为什么Haskell和许多其他monad实现在绑定方面定义它们?它似乎没有基于连接的定义那么有用。
答案 0 :(得分:9)
有可能:
join :: Monad m => m (m a) -> m a
join m = (m >>= id)
请注意>>=
:
(>>=) :: m b -> (b -> m c) -> m c
-- choosing b ~ m a , c ~ a
(>>=) :: m (m a) -> (m a -> m a) -> m a
所以我们可以为第二个参数正确选择id
。
答案 1 :(得分:6)
是的,这很简单:
join m = m >>= id
答案 2 :(得分:3)
绑定(>>=
)确实“删除了一个图层”:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
直观地说,它“从a
”中获取了一些m a
,然后将其提供给a -> m b
函数,然后从结果中生成一个m b
。
人们通常会说它需要函数参数在m
中再次包装它的输出,但事实并非如此。它要求函数的输出是包含在m
中的东西,但包装来自何处并不重要。
在实施join
的情况下,我们从“双重包装”开始:m (m a)
。我们可以将其插入到绑定的签名中,并立即找出绑定“双包装”值时可以使用的函数类型:
m (m a) -> (m a -> m b) -> m b
现在与bind一起使用的函数将接收一个已经包含在m
中的值。所以我们不必“重新包装”任何东西;如果我们将其修改为未经修改,那么它已经是输出的正确类型。实际上是“删除了一层包装” - 这适用于任何层,但最后一层。
这告诉我们,我们必须与id
绑定:
join = (>>= id)
答案 3 :(得分:1)
虽然这个问题已经得到了充分的回答,但我认为任何Haskell新人都值得注意以下几点。
在Haskell中学习monad时,你会看到的第一件事就是列表的定义:
instance Monad [a] where
return x = [x]
xs >>= f = concatMap f xs
在这里,我们看到bind for lists的功能等同于concatMap,只是翻转了参数。在检查类型时这是有意义的:
concatMap :: (a -> [b]) -> [a] -> [b]
bind :: Monad m => (a -> m b ) -> m a -> m b -- (>>=) flips the arguments
直觉上,列表join
的定义等同于
concat :: [[a]] -> [a]
。
函数的名称可能会显而易见,但concat
可以通过不更改任何内部列表从concatMap
恢复,以取消“地图”部分concatMap
:
concat xs = concatMap id xs -- or simply 'concat = concatMap id'
同样的属性适用于一般的monad:
join x = x >>= id -- or 'join = bind id'
答案 4 :(得分:0)
join 可以用 bind 来定义吗?
TL;DR 回答:是的。
join ∷ (Monad m) ⇒ m (m a) → m a
join = (=<<) id
更长的答案: 为了添加一些尚未提及的微妙之处,我将提供一个新答案,首先扩展 Lee 的 answer,因为值得注意的是,他们的答案可以简化。从原文开始:
join ∷ (Monad m) ⇒ m (m a) → m a
join m = m >>= id
可以寻找Eta conversion(η-转换)机会来定义函数point-free。为此,我们首先要重写不带中缀 >>=
的函数定义(如果我们首先通过名称 >>=
调用 bind
,可能会这样做)。>
join m = (>>=) m id
现在观察,如果我们使用 flip
函数,回忆一下:
-- defined in Data.Function
-- for a function of two arguments, swap their order
flip ∷ (a → b → c) → b → a → c
flip f b a = f a b
现在可以使用 flip
将 m
放在适当的位置以减少 η:
join m = (flip (>>=)) id m
应用 η 减少:
join = (flip (>>=)) id
现在注意到 flip (>>=)
可以替换为 (=<<)
(在 Control.Monad
中定义):
join = (=<<) id
最后我们可以看到更短的、无点的定义:
join ∷ (Monad m) ⇒ m (m a) → m a
join = (=<<) id
其中 (=<<)
具有类型:
(=<<) ∷ ∀ (m ∷ * -> *) a b. (Monad m) ⇒ (a → m b) → m a → m b
在进程中被实例化为:
(=<<) ∷ (m a → m a) → m (m a) → m a
此外,人们可能还会注意到,如果我们将上面的代码放回中缀形式,flip
将变为隐式,并且我们得到与 answer 相同的最终 Ben:< /p>
join = (>>= id)