【问题标题】:With monads, can join be defined in terms of bind?使用 monad,可以用 bind 来定义 join 吗?
【发布时间】:2016-03-27 16:17:07
【问题描述】:

在 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 的函数。绑定可以根据连接来定义,但是反过来可能吗? join可以用bind来定义吗?

如果没有加入,我不知道如果我以某种方式获得了“两次包装”的单子值,我会做什么,m (m a) - 没有任何仿函数或单子操作“删除任何层”,可以这么说.如果这是不可能的,为什么 Haskell 和许多其他 monad 实现用绑定来定义它们?严格来说,它似乎不如基于连接的定义有用。

【问题讨论】:

  • 注意“也”:如果无法根据绑定来定义连接,则它必须成为Monad 的成员(或者不会被定义)适用于所有单子),但事实并非如此。

标签: haskell monads


【解决方案1】:

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 (η-conversion) 机会来定义函数point-free。为此,我们首先要重写没有中缀 &gt;&gt;= 的函数定义(如果我们首先通过名称 bind 调用 &gt;&gt;= 可能会这样做)。

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 (&gt;&gt;=) 可以替换为(=&lt;&lt;)(在Control.Monad 中定义):

join = (=<<) id

我们终于可以看到更短、无点的定义了:

join ∷ (Monad m) ⇒ m (m a) → m a
join = (=<<) id

(=&lt;&lt;) 的类型:

(=<<) ∷ ∀ (m ∷ * -> *) a b. (Monad m) ⇒ (a → m b) → m a → m b

在这个过程中被实例化为:

(=<<) ∷ (m a → m a) → m (m a) → m a

此外,人们可能还会注意到,如果我们将上面的代码放回中缀形式,flip 将变为隐式,我们会得到与 Ben 相同的最终 answer

join = (>>= id)

【讨论】:

  • 如何简化?无点并不总是更简单或更好。
  • 我绝对同意无点肯定不是必然“更简单”也不是“更好”,但在这种情况下,我认为 η-reduction 使最终回答更简单。例如,从我的回答中的链接中,有人可能会争辩说:sum = foldr (+) 0sum' xs = foldr (+) 0 xs“更简单”和“更好”因为sum 通过省略多余的内容来强调函数定义中发生的事情的本质。我意识到“更简单”可以被主观解释,所以我明白你为什么会问,但我觉得我的回答是有道理的。
  • 另外(我的字符空间用完了),很多人从列表的concatMap 示例开始学习在 Haskell 中使用 monad(甚至在此处的答案之一中也这样做了) , 并且碰巧concatMap 的参数顺序与(=&lt;&lt;) 相同,因此人们可能会认为concatMap id 比执行相同操作但通过添加一个更改定义的版本“更好” flip 并不必要地命名事物。在旁注中,我只想说“你好”@dfeuer,我非常感谢你在 Haskell 中所做的所有工作,谢谢你的问题 :)
【解决方案2】:

虽然这个问题已经得到了充分的回答,但我认为对于任何 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]] -&gt; [a],这也很直观。

函数的名称可能会让它有点明显,但concat 可以通过保持内部列表原样从concatMap 恢复,以便取消concatMap 的“映射”部分:

concatMap       id xs      
  = concat (map id xs)
  = concat (    id xs)
  = concat         xs      -- or simply 'concat = concatMap id'

同样的属性也适用于一般的 monad:

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

【讨论】:

    【解决方案3】:

    Bind (&gt;&gt;=) 实际上是“移除一层”:

    (>>=) :: Monad m => m a -> (a -> m b) -> m b
    

    直观地说,它“从m a 中获取一些as”,然后提供给a -&gt; m b 函数,然后从结果中生成一个m b

    人们通常说它需要函数参数在m 中再次包装其输出,但实际上并非如此。它要求函数的输出包裹在m中的东西,但包裹来自哪里并不重要。

    在实现join 的情况下,我们从“双重包装”的东西开始:m (m a)。我们可以将它插入到 bind 的签名中,并立即找出我们在绑定“双重包装”值时可以使用的函数类型:

    m (m a) -> (m a -> m b) -> m b
    

    现在这里与 bind 一起使用的函数将接收一个 已经 包裹在 m 中的值。所以我们不必“重新包装”任何东西;如果我们不加修改地返回它,它已经是输出的正确类型。实际上,这就是“去除了一层包装”——这适用于除最后一层之外的任何层。

    这告诉我们我们只需要绑定id

    join = (>>= id)
    

    【讨论】:

      【解决方案4】:

      有可能:

      join :: Monad m => m (m a) -> m a
      join m = (m >>= id)
      

      注意&gt;&gt;= 的棘手实例化:

      (>>=) :: m b -> (b -> m c) -> m c
      -- choosing b ~ m a , c ~ a
      (>>=) :: m (m a) -> (m a -> m a) -> m a
      

      所以我们可以正确选择id 作为第二个参数。

      【讨论】:

        【解决方案5】:

        是的,这很简单:

        join m = m >>= id
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-01
          • 1970-01-01
          • 1970-01-01
          • 2021-07-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多