Monad与Join()而不是Bind()
Monads通常会依次解释return
和bind
。 但是,我收集你也可以在join
(和fmap
?)方面实现bind
在缺乏一流功能的编程语言中, bind
使用起来非常尴尬。 另一方面, join
看起来相当容易。
然而,我并不完全确定我明白join
如何运作。 显然,它有[Haskell]类型
join :: Monad m => m (m x) -> m x
对于monad列表,这是简单而明显的concat
。 但对于一般的monad来说,这种方法实际上在做什么呢? 我看到它对类型签名有什么作用,但我试图弄清楚如何在Java或类似语言中编写类似的东西。
(其实,这很容易:我不会,因为泛型被打破了;-)但原则上这个问题仍然存在......)
哎呀。 它看起来像之前已经被问过:
Monad连接功能
有人可以使用return
, fmap
和join
来概述常见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
值生成v2mw
的w
策略 - 准备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可以被认为是一个“容器”,那么return
和join
都有非常明显的语义。 return
生成一个1元素的容器,并且join
将一个容器的容器变成一个容器。 没有什么难的。
因此,让我们关注更自然地被认为是“行动”的monad。 在这种情况下, mx
是某种行为,当你“执行”它时会产生x
类型的值。 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 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