对于 monad,可以根据 bind 来定义 join 吗?
With monads, can join be defined in terms of bind?
在 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 来定义,但反过来可能吗? join可以用bind来定义吗?
如果没有加入,我不知道如果我以某种方式得到一个 "twice wrapped" monadic 值,m (m a)
- none 函子或 monad 操作,我会怎么做"remove any layers",可以这么说。如果这不可能,为什么 Haskell 和许多其他 monad 实现根据绑定来定义它们?它似乎严格来说没有基于连接的定义有用。
是的,这很简单:
join m = m >>= id
有可能:
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
作为第二个参数。
Bind (>>=
) 事实上 "remove a layer":
(>>=) :: Monad m => m a -> (a -> m b) -> m b
直觉上它 "gets some a
s out of the m a
",然后提供给 a -> m b
函数,然后从结果中生成一个 m b
。
人们通常说它需要函数参数在m
中再次包装它的输出,但实际上并非如此。它要求函数的输出 是 包裹在 m
中的东西,但包裹来自哪里并不重要。
在实施 join
的情况下,我们从 "double-wrapped" 开始:m (m a)
。我们可以将其插入到绑定的签名中,并立即找出绑定 "double-wrapped" 值时可以使用的函数类型:
m (m a) -> (m a -> m b) -> m b
现在这里与 bind 一起使用的函数将接收一个 已经 包装在 m
中的值。所以我们不需要 "re-wrap" 任何东西;如果我们 return 它未经修改,它已经是输出的正确类型。实际上是 "removed one layer of wrapping" - 这适用于除最后一层以外的任何层。
所以这告诉我们我们只需要绑定 id
:
join = (>>= id)
虽然这个问题已经得到了充分的回答,但我认为对于任何 Haskell 新人来说,以下内容值得注意。
在 Haskell 中学习 monad 时首先看到的是列表的定义:
instance Monad [] where
return x = [x]
xs >>= f = concatMap f xs
在这里,我们看到 bind 对列表的功能等同于 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
中恢复,以抵消 "map" concatMap
的一部分:
concatMap id xs
= concat (map id xs)
= concat ( id xs)
= concat xs -- or simply 'concat = concatMap id'
同样的 属性 通常适用于 monads:
join x = x >>= id -- or 'join = bind id'
这真的来自于
bind f m = join (fmap f m)
所以
bind id m = join (fmap id m) -- bind id = join . fmap id
= join ( id m) -- = join . id
= join m -- = join
因为所有的 monad 首先是 Functor,并且根据 Functor 定律 fmap id === id
。
Can join be defined in terms of bind?
TL;DR 回答:是的。
join ∷ (Monad m) ⇒ m (m a) → m a
join = (=<<) id
较长的答案:
为了添加一些尚未提及的微妙之处,我将提供一个新答案,首先扩展 Lee's ,因为值得注意的是他们的答案可以简化。从原文开始:
join ∷ (Monad m) ⇒ m (m a) → m a
join m = m >>= id
可以找 Eta conversion (η-conversion) opportunity to make the function definition 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
就变成隐含的,我们得到相同的最终 as Ben :
join = (>>= id)
在 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 来定义,但反过来可能吗? join可以用bind来定义吗?
如果没有加入,我不知道如果我以某种方式得到一个 "twice wrapped" monadic 值,m (m a)
- none 函子或 monad 操作,我会怎么做"remove any layers",可以这么说。如果这不可能,为什么 Haskell 和许多其他 monad 实现根据绑定来定义它们?它似乎严格来说没有基于连接的定义有用。
是的,这很简单:
join m = m >>= id
有可能:
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
作为第二个参数。
Bind (>>=
) 事实上 "remove a layer":
(>>=) :: Monad m => m a -> (a -> m b) -> m b
直觉上它 "gets some a
s out of the m a
",然后提供给 a -> m b
函数,然后从结果中生成一个 m b
。
人们通常说它需要函数参数在m
中再次包装它的输出,但实际上并非如此。它要求函数的输出 是 包裹在 m
中的东西,但包裹来自哪里并不重要。
在实施 join
的情况下,我们从 "double-wrapped" 开始:m (m a)
。我们可以将其插入到绑定的签名中,并立即找出绑定 "double-wrapped" 值时可以使用的函数类型:
m (m a) -> (m a -> m b) -> m b
现在这里与 bind 一起使用的函数将接收一个 已经 包装在 m
中的值。所以我们不需要 "re-wrap" 任何东西;如果我们 return 它未经修改,它已经是输出的正确类型。实际上是 "removed one layer of wrapping" - 这适用于除最后一层以外的任何层。
所以这告诉我们我们只需要绑定 id
:
join = (>>= id)
虽然这个问题已经得到了充分的回答,但我认为对于任何 Haskell 新人来说,以下内容值得注意。
在 Haskell 中学习 monad 时首先看到的是列表的定义:
instance Monad [] where
return x = [x]
xs >>= f = concatMap f xs
在这里,我们看到 bind 对列表的功能等同于 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
中恢复,以抵消 "map" concatMap
的一部分:
concatMap id xs
= concat (map id xs)
= concat ( id xs)
= concat xs -- or simply 'concat = concatMap id'
同样的 属性 通常适用于 monads:
join x = x >>= id -- or 'join = bind id'
这真的来自于
bind f m = join (fmap f m)
所以
bind id m = join (fmap id m) -- bind id = join . fmap id
= join ( id m) -- = join . id
= join m -- = join
因为所有的 monad 首先是 Functor,并且根据 Functor 定律 fmap id === id
。
Can join be defined in terms of bind?
TL;DR 回答:是的。
join ∷ (Monad m) ⇒ m (m a) → m a
join = (=<<) id
较长的答案:
为了添加一些尚未提及的微妙之处,我将提供一个新答案,首先扩展 Lee's
join ∷ (Monad m) ⇒ m (m a) → m a
join m = m >>= id
可以找 Eta conversion (η-conversion) opportunity to make the function definition 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
就变成隐含的,我们得到相同的最终
join = (>>= id)