Monad通常以 return
和 bind
轮流解释 . 但是,我收集你也可以用 join
(和 fmap
?)来实现 bind
在缺乏一流功能的编程语言中, bind
使用起来非常难以捉摸 . 另一方面, join
看起来很容易 .
但是,我不完全确定我理解 join
是如何工作的 . 显然,它有[Haskell]类型
join :: Monad m => m (m x) -> m x
对于列表monad,这很简单,显然是 concat
. 但是对于一般的monad来说,这种方法在操作上实际上做了什么?我看到它对类型签名的作用,但是我在这里写了类似的东西,比如Java或类似的东西 .
(实际上,这很容易:我不会 . 因为仿制药已经坏了 . ;-)但原则问题仍然存在......)
哎呀 . 看起来之前有人问过:
有人可以使用 return
, fmap
和 join
勾勒出常见monad的一些实现吗? (即,根本没有提到 >>=
. )我想也许这可能有助于它沉入我愚蠢的大脑......
7 回答
这是莫纳德在一张照片中解释的 . 绿色类别中的2个功能不可组合,当映射到蓝色类别时(严格来说,它们是一个类别),它们变得可组合 . Monad是关于将
T -> Monad<U>
类型的函数转换为函数Monad<T> -> Monad<U>
.如果没有管道隐喻的深度,我可能建议将典型的monad
m
读为"strategy to produce a",因此类型m value
是第一类"strategy to produce a value" . 不同的计算或外部交互概念需要不同类型的策略,但一般概念需要一些规则的结构才有意义:如果你已经有了一个值,那么你有一个策略来产生一个值(
return :: v -> m v
),除了产生你拥有的值之外什么都没有;如果你有一个将一种 Value 转换成另一种 Value 的函数,只需等待策略提供其 Value ,然后转换它,就可以将其提升为策略(
fmap :: (v -> u) -> m v -> m u
);如果你有一个生成策略来生成一个值的策略,那么你可以构建一个策略来产生一个值(
join :: m (m v) -> m v
),它遵循外部策略,直到它产生内部策略,然后遵循内部策略一直到a值 .让我们举个例子:叶子标记的二叉树......
......代表通过掷硬币来制作东西的策略 . 如果策略是
Leaf v
,那么你的v
;如果策略是Node h t
,如果硬币显示为"heads",t
,如果它是"tails",则抛出一枚硬币继续按策略h
.策略生成策略是一棵带有树标记叶子的树:代替每一片这样的叶子,我们可以只在树上标记它的树...
......当然,我们有
fmap
,它只是重新开始 .这是制定战略以产生
Int
的战略 .掷硬币:如果它是“头”,则抛出另一枚硬币来决定两种策略(分别产生“掷硬币 生产环境 0或 生产环境 1”或“ 生产环境 2”);如果它是“尾巴”产生第三个(“投掷硬币 生产环境 3或掷硬币4或5”) .
显然
join
是制定战略产生Int
.我们正在利用的是"strategy to produce a value"本身可以被视为一种 Value . 在Haskell中,策略作为值的嵌入是沉默的,但在英语中,我使用引号来区分使用策略而不仅仅是谈论它 .
join
运算符表示策略"somehow produce then follow a strategy",或“如果您被告知策略,则可以使用它” .(Meta.I 'm not sure whether this 1123334 approach is a suitably generic way to think about monads and the value/computation distinction, or whether it'只是另一个糟糕的比喻 . 我确实发现叶子标记的树状类型是直觉的有用来源,这可能不是一个惊喜,因为它们是免费的monad,只有足够的结构才能成为monad ,但没有更多 . )
PS“绑定”的类型
说“如果你有一个 生产环境
v
的策略,并且每个v产生一个后续策略来生成w
,那么你就有了生成w
的策略” . 我们怎样才能从join
中获取?我们可以通过
v2mw
重新标记我们的v
生产环境 策略,而不是每个v
值生成w
生成策略,该策略从它开始 - 准备好join
!好的,所以回答你自己的问题并不是一个很好的形式,但我会记下我的想法,以防它启发任何其他人 . (我对此表示怀疑...)
如果monad可以被认为是"container",那么
return
和join
都有非常明显的语义 .return
生成1个元素的容器,join
将容器容器转换为单个容器 . 没什么难的 .因此,让我们专注于更自然地被认为是"actions"的monad . 在这种情况下,
m x
是某种动作,当你"execute"它时会产生x
类型的值 .return x
没有什么特别的,然后产生x
.fmap f
采取产生x
的操作,并构造一个计算x
的操作,然后对其应用f
,并返回结果 . 到现在为止还挺好 .很明显,如果
f
本身生成一个动作,那么你最终得到的是m (m x)
. 也就是说,计算另一个动作的动作 . 在某种程度上,比起采取行动和"function that produces an action"等的>>=
函数,你可能更容易理解 .所以,从逻辑上讲,似乎
join
会运行第一个动作,执行它产生的动作,然后运行它 . (或者更确切地说,join
会返回一个动作来执行我刚才描述的动作,如果你想分裂头发 . )这似乎是中心思想 . 要实现
join
,您希望运行一个操作,然后再为您提供另一个操作,然后运行该操作 . (无论"run"对于这个特殊的monad来说意味着什么 . )鉴于这种见解,我可以尝试编写一些
join
实现:如果外部操作是
Nothing
,则返回Nothing
,否则返回内部操作 . 再说一次,Maybe
更像是一个容器而不是一个动作,所以让我们试试别的......那......无痛苦 .
Reader
实际上只是一个采用全局状态的函数,然后才返回其结果 . 因此,对于取消堆栈,您将全局状态应用于外部操作,该操作将返回新的Reader
. 然后,您也可以将状态应用于此内部函数 .在某种程度上,它可能比通常的方式更容易:
现在,哪一个是读者功能,哪一个是计算下一个读者的功能......?
现在让我们试试好老的
State
monad . 这里每个函数都将初始状态作为输入,但也返回一个新状态及其输出 .那不是太难 . 它基本上运行然后运行 .
我现在要打算停止打字了 . 随意指出我的例子中的所有故障和拼写错误...: - /
我发现很多关于monad的解释说“你不必知道关于类别理论的任何事情,真的,只要将monads想象为墨西哥卷饼/太空服/无论什么” .
真的,那个为我揭开神秘面纱的文章只是说了什么类别,在类别方面描述了monad(包括加入和绑定),并没有打扰任何虚假的比喻:
我认为这篇文章非常易读,不需要太多的数学知识 .
调用
fmap (f :: a -> m b) (x ::
ma)
会产生值(y ::
m(m b))
因此使用join
来获取值(z :: m b)
非常自然 .然后bind被简单地定义为
bind ma f = join (fmap f ma)
,从而实现了(:: a -> m b)
的Kleisly compositionality函数,这就是它真正的全部内容:所以,使用
flip bind = (=<<)
,we have询问Haskell中的类型签名是什么,就像询问Java中的接口是什么一样 .
从某种字面意义上讲,它“不是” . (当然,你通常会有一些与之相关的目的,这主要在你的脑海中,而且大部分都不在实现中 . )
在这两种情况下,您都要声明语言中符号的合法序列,这些符号将在以后的定义中使用 .
当然,在Java中,我想你可以说一个接口对应一个类型签名,它将在VM中逐字实现 . 您可以通过这种方式获得一些多态性 - 您可以定义接受接口的名称,并且可以为接受不同接口的名称提供不同的定义 . 在Haskell中会发生类似的事情,您可以在其中为名称提供声明,该声明接受一种类型,然后为该名称提供另一种声明处理不同类型的声明 .