使用monads,可以根据绑定来定义连接吗?

时间:2015-12-21 14:41:15

标签: haskell monads

在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实现在绑定方面定义它们?它似乎没有基于连接的定义那么有用。

5 个答案:

答案 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

更长的答案: 为了添加一些尚未提及的微妙之处,我将提供一个新答案,首先扩展 Leeanswer,因为值得注意的是,他们的答案可以简化。从原文开始:

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

现在可以使用 flipm 放在适当的位置以减少 η:

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)