Monad与Join()而不是Bind()

Monads通常会被解释为returnbind 。 不过,我收集你也可以通过join (和fmap ?)来实现bind

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

但是,我并不完全确定我明白join方式。 显然,它有[Haskell]types

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

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

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


哎呀。 看起来这已经被问到过了:

Monad连接function

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

如果没有探索隐喻的深度,我可能会build议读一个典型的monad m作为“产生a的策略”,所以m valuem value是一个“产生价值的策略”。 计算或外部交互的不同概念需要不同types的策略,但是一般的概念需要一些规则的结构才有意义:

  • 如果你已经有一个值,那么你有一个策略来产生一个值( 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如果是“尾巴”,则继续。

 instance Monad Tree where return = Leaf 

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

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

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

 instance Functor Tree where fmap f (Leaf x) = Leaf (fx) fmap f (Node ht) = Node (fmap fh) (fmap ft) 

这是制定一个产生一个Int的策略的策略。

树的树

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

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

在这里输入图像说明

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

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

PS“绑定”

 (>>=) :: mv -> (v -> mw) -> mw 

说:“如果你有一个策略产生一个v ,并且每个va后续策略产生一个w ,那么你就有一个产生w的策略。 我们怎样才能捕捉join

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

我们可以通过v2mw来重新生成我们的生产策略,而不是每个v值都生成v2mw生产策略 – 准备join

 join = concat -- [] join f = \x -> fxx -- (e ->) join f = \s -> let (f', s') = fs 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元素的容器,并将一个容器的容器变成一个容器。 没有什么难的。

那么让我们把焦点放在更自然地被认为是“行动”的monad上。 在这种情况下, mx是某种types的动作,当你“执行”时它会产生一个xtypes的值。 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 sx = Reader (s -> x) join (Reader f) = Reader (\ s -> let Reader g = fs in gs) 

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

从某种意义上说,这可能比通常的方式更简单

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

现在,哪一个是阅读器function,哪一个是计算下一个阅读器的function?

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

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

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

我现在要停止打字了。 随意指出我的例子中所有的故障和错别字: – /

我发现monads有很多解释说:“你不必知道任何有关类别理论的知识,实际上,只是把monad当作卷饼/太空服/不pipe。

真的,那些为我揭秘monads的文章只是说了什么类别,用类别来描述单子(包括连接和绑定),并且不打扰任何假的隐喻:

我认为这篇文章是非常可读的,没有太多的math知识要求。

调用fmap (f :: a -> mb) (x :: m a)会产生值(y :: m (mb))所以使用join来返回值(z :: mb)非常自然的事情

然后绑定被简单地定义为bind ma f = join (fmap f ma) ,从而实现(:: a -> mb)变体的函数的Kleisly组合性,这就是它的真正意义所在:

 ma `bind` (f >=> g) = (ma `bind` f) `bind` g -- bind = (>>=) = (`bind` g) . (`bind` f) $ ma = join . fmap g . join . fmap f $ ma 

所以,用flip bind = (=<<) , 我们有

  ((g <=< f) =<<) = (g =<<) . (f =<<) = join . (g <$>) . join . (f <$>) 

在这里输入图像说明

询问Haskell 的types签名是什么,而不是问Java 中的一个接口。

它在某种意义上说是“不”。 (当然,虽然你通常会有一些与之相关的目的,但大部分都是在你的脑海里,大部分都不在实现中。)

在这两种情况下,您都要用以后定义中使用的语言来声明合法的符号序列。

当然,在Java中,我想你可以说一个接口对应于一个types签名,这个types签名将会在虚拟机中逐字地实现。 你可以用这种方法得到一些多态性 – 你可以定义一个接受接口的名字,并且你可以为接受不同接口的名字提供一个不同的定义。 在Haskell中发生了类似的情况,你可以在这里提供一个名字的声明,它接受一个types,然后为另一个types声明另一个声明。

这是Monad在一张照片中解释的。 绿色类别中的2个函数在被映射到蓝色类别(严格来说,它们是一个类别)时是不可组合的,因此它们变成可组合的。 Monad就是将typesT -> Monad<U>的函数转换为Monad<T> -> Monad<U>的函数。

Monad在一张照片中解释道。