好的,所以回答你自己的问题并不是一个很好的形式,但我会记下我的想法,以防它启发其他人。(我对此表示怀疑...)
如果一个 monad 可以被认为是一个“容器”,那么两者return
都有join
非常明显的语义。return
生成一个单元素容器,并将join
容器的容器变成单个容器。这没什么难的。
所以让我们专注于更自然地被认为是“动作”的单子。在这种情况下,m x
是某种动作,x
当你“执行”它时会产生一个类型的值。return x
没有什么特别的,然后产生x
. fmap f
接受一个产生 的动作x
,并构造一个计算x
然后应用f
到它的动作,并返回结果。到目前为止,一切都很好。
很明显,如果f
它自己产生一个动作,那么你最终得到的是m (m x)
. 也就是说,一个计算另一个动作的动作。>>=
在某种程度上,这可能比采取行动的函数和“产生行动的函数”等更容易让你思考。
所以,从逻辑上讲,它似乎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)
那是……无痛的。AReader
实际上只是一个接受全局状态的函数,然后才返回其结果。因此,要取消堆栈,您将全局状态应用于外部操作,该操作返回一个新的Reader
. 然后,您也将状态应用于此内部函数。
在某种程度上,它可能比通常的方式更容易:
Reader f >>= g = Reader (\ s -> let x = f s in g x)
现在,哪个是 reader 函数,哪个是计算下一个 reader 的函数......?
现在让我们试试旧的State
单子。这里每个函数都将初始状态作为输入,但也会返回一个新状态及其输出。
data State s x = State (s -> (s, x))
join (State f) = State (\ s0 -> let (s1, State g) = f s0 in g s1)
那不是太难。它基本上是运行然后运行。
我现在要停止打字了。随时指出我的示例中的所有故障和错别字... :-/