Monad与Join()而不是Bind()

Monads通常会依次解释returnbind 。 但是,我收集你也可以在join (和fmap ?)方面实现bind

在缺乏一流功能的编程语言中, bind使用起来非常尴尬。 另一方面, join看起来相当容易。

然而,我并不完全确定我明白join如何运作。 显然,它有[Haskell]类型

join :: Monad m => m (m x) -> m x

对于monad列表,这是简单而明显的concat 。 但对于一般的monad来说,这种方法实际上在做什么呢? 我看到它对类型签名有什么作用,但我试图弄清楚如何在Java或类似语言中编写类似的东西。

(其实,这很容易:我不会,因为泛型被打破了;-)但原则上这个问题仍然存在......)


哎呀。 它看起来像之前已经被问过:

Monad连接功能

有人可以使用returnfmapjoin来概述常见monads的一些实现吗? (也就是说,根本不会提及>>= )我想这也许有助于它沉入我的愚蠢的大脑中......


如果没有深入隐喻的深度,我是否可以建议将典型的monad m作为“产生a的策略”,因此m value类型的m value是一种“产生价值的策略”。 计算或外部交互的不同概念需要不同类型的策略,但一般概念需要一些规则的结构才有意义:

  • 如果你已经有了一个值,那么你有一个策略来产生一个值( return :: v -> mv ),除了产生你所拥有的值外,
  • 如果你有一个函数将一种价值转化为另一种价值,那么你可以通过等待策略传递其价值,然后转化为策略( fmap :: (v -> u) -> mv -> mu )它;
  • 如果你有策略来产生一个价值的策略,那么你可以构造一个策略来产生一个值( join :: m (mv) -> mv ),它遵循外部策略,直到它产生内部策略,然后跟随这种内在策略一直到价值。
  • 让我们举个例子:叶子标记的二叉树...

    data Tree v = Leaf v | Node (Tree v) (Tree v)
    

    ......代表投掷硬币来制作东西的策略。 如果策略是Leaf v ,那么就是你的v ; 如果策略Node ht ,你掷硬币,并通过战略继续h如果硬币显示为“头”, t ,如果它的“尾巴”。

    instance Monad Tree where
      return = Leaf
    

    制定策略的策略是带有树状标签的树叶:代替每个这样的树叶,我们可以植入标签树中的树木......

      join (Leaf tree) = tree
      join (Node h t)  = Node (join h) (join t)
    

    ...当然,我们有fmap ,只是fmap离开。

    instance Functor Tree where
      fmap f (Leaf x)    = Leaf (f x)
      fmap f (Node h t)  = Node (fmap f h) (fmap f t)
    

    以下是制定产生Int的策略的策略。

    投掷一枚硬币:如果它是“头”,则投掷另一枚硬币以决定两种策略(分别产生“投掷硬币产生0还是产生1”或“产生2”); 如果它是“尾巴”产生第三(“掷硬币生产3或掷硬币4或5”)。

    这显然join了制定一个Int战略。

    我们正在利用的是,“产生价值的策略”本身可以被看作是一种价值。 在Haskell中,策略作为值的嵌入是沉默的,但在英语中,我使用引号来区分使用策略与仅仅谈论策略。 join运算符表达策略“以某种方式产生,然后遵循策略”,或者“如果你被告知策略,那么你可以使用它”。

    (Meta。我不确定这种“策略”方法是否适合考虑单子和价值/计算区别,或者它是否只是另一个糟糕的隐喻。我发现叶子标记的树状类型是有用的直觉的来源,这可能不是一个惊喜,因为他们是免费的单体,只有足够的结构可以成为单子,但没有更多。)

    PS“绑定”的类型

    (>>=) :: m v -> (v -> m w) -> m w
    

    他说:“如果你有一个产生v的策略,并且对于每个产生w后续策略,那么你就有一个产生w的策略。 我们如何从join方面捕捉到这一点?

    mv >>= v2mw = join (fmap v2mw mv)
    

    我们可以通过v2mw来重新标记我们的v生成策略,而不是每个v值生成v2mww策略 - 准备join


    join = concat -- []
    join f = x -> f x x -- (e ->)
    join f = s -> let (f', s') = f s in f' s' -- State
    join (Just (Just a)) = Just a; join _ = Nothing -- Maybe
    join (Identity (Identity a)) = Identity a -- Identity
    join (Right (Right a)) = Right a; join (Right (Left e)) = Left e; 
                                      join (Left e) = Left e -- Either
    join ((a, m), m') = (a, m' `mappend` m) -- Writer
    join f = k -> f (f' -> f' k) -- Cont
    

    好的,所以回答你自己的问题并不是一个好的方式,但我会记下我的想法,以便启发其他人。 (我对此表示怀疑...)

    如果monad可以被认为是一个“容器”,那么returnjoin都有非常明显的语义。 return生成一个1元素的容器,并且join将一个容器的容器变成一个容器。 没有什么难的。

    因此,让我们关注更自然地被认为是“行动”的monad。 在这种情况下, mx是某种行为,当你“执行”它时会产生x类型的值。 return x并没有什么特别的,然后产生xfmap f采用一个产生x的动作,并构造一个计算x的动作,然后将f到它并返回结果。 到现在为止还挺好。

    很明显,如果f本身产生一个动作,那么你最终得到的是m (mx) 。 也就是说,计算另一个动作的动作。 从某种意义上来说,这可能比>>= ”函数和“产生动作的函数”等更简单。

    因此,从逻辑上讲,似乎join会运行第一个动作,执行它产生的动作,然后运行它。 (或者说,如果你想分开头发, join会返回一个我刚才描述的动作。)

    这似乎是中心思想。 为了实现join ,你想运行一个动作,然后给你另一个动作,然后你运行它。 (无论“运行”对于这个特殊的monad来说意味着什么)。

    鉴于这种见解,我可以尝试编写一些join实现:

    join Nothing = Nothing
    join (Just mx) = mx
    

    如果外部动作是Nothing ,则返回Nothing ,否则返回内部动作。 然后再次, Maybe是一个容器而不是一个动作,所以让我们尝试一些其他的东西......

    newtype Reader s x = Reader (s -> x)
    
    join (Reader f) = Reader ( s -> let Reader g = f s in g s)
    

    那是......无痛。 Reader实际上只是一个接受全局状态的函数,然后才返回结果。 因此,为了卸载,您将全局状态应用于外部操作,这会返回一个新的Reader 。 然后,将状态应用于该内部函数。

    在某种程度上,它可能比通常的方式更容易:

    Reader f >>= g = Reader ( s -> let x = f s in g x)
    

    现在,哪一个是阅读器功能,哪一个是计算下一个阅读器的功能......?

    现在我们来试试这个好的旧State monad。 在这里,每个函数都将一个初始状态作为输入,同时返回一个新的状态以及其输出。

    data State s x = State (s -> (s, x))
    
    join (State f) = State ( s0 -> let (s1, State g) = f s0 in g s1)
    

    这并不难。 它基本上运行,然后运行。

    我现在要停止打字了。 随意指出我例子中的所有毛病和拼写错误...: - /

    链接地址: http://www.djcxy.com/p/47665.html

    上一篇: Monads with Join() instead of Bind()

    下一篇: What is the compelling scenario for using Monads in C#