Monad与Join()而不是Bind()
Monads通常会被解释为return
和bind
。 不过,我收集你也可以通过join
(和fmap
?)来实现bind
在缺乏一streamfunction的编程语言中, bind
使用起来非常尴尬。 join
,另一方面,看起来相当容易。
但是,我并不完全确定我明白join
方式。 显然,它有[Haskell]types
join :: Monad m => m(mx) - > mx
对于单子列表,这是平凡而明显的concat
。 但是对于一般的monad来说,这个方法实际上在做什么呢? 我看到它对types签名有什么作用,但是我想弄清楚如何在Java或类似语言中写这样的东西。
(其实,这很容易:我不会,因为generics被打破了;-)但原则上这个问题仍然存在…)
哎呀。 看起来这已经被问到过了:
Monad连接function
有人可以使用return
, fmap
和join
来概述常见monads的一些实现吗? (也就是说,根本不提>>=
。)我认为也许这可能会帮助它沉入我的愚蠢的大脑中。
如果没有探索隐喻的深度,我可能会build议读一个典型的monad m
作为“产生a的策略”,所以m value
型m 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可以被认为是一个“容器”,那么return
和join
都有相当明显的语义。 return
生成一个1元素的容器,并将一个容器的容器变成一个容器。 没有什么难的。
那么让我们把焦点放在更自然地被认为是“行动”的monad上。 在这种情况下, mx
是某种types的动作,当你“执行”时它会产生一个x
types的值。 return x
没有什么特别的,然后产生x
。 fmap 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>
的函数。