qea*_*adz 5 monads state-monad
在过去的几个月里,我一直在花一些空闲时间来阅读有关 Monad 的文章。自从大学时代以来,我就没有使用过函数式语言。所以我不太记得 Haskell,当然也不知道 Scalaz。
学习洋葱、墨西哥卷饼和三明治,同时尝试将这些食物与大量 Haskell 代码关联起来,这是一段艰难的时光。幸运的是,我偶然发现了两篇关键文章,这让我大吃一惊:图片中的单子,以及另一个来自命令式编码背景的人,他简单地写了相当于以下内容的内容:
a -> b becomes m[a] -> m[b] (functor)
a -> m[b] becomes m[a] -> m[b] (monad)
and applicative is just a "function with context"
Run Code Online (Sandbox Code Playgroud)
连同描述bind目的的最简单的句子一起,内函子的许多类别爆炸并变得简单。
Identity、List、Maybe 等突然变得有意义了。在这些知识的推动下,我开始尝试使用一元类型在 C++ 中尝试一些 TMP,看看它会如何实现。
很快我就想传播状态。我想我现在读到的关于状态 monad 和 monad 转换器的文章比我一开始读的关于 monad 的文章还要多,但我一生都无法理解它们。我确信许多键盘已经磨损了,因为这些主题上输入的所有单词都回答了像我这样的人。
但我请你再受一点苦。
a -> s -> (b, s)
Run Code Online (Sandbox Code Playgroud)
函数接受一个值和某个状态,返回一个新值和(可能)修改后的状态。Bind 显然需要将返回值和状态传播到下一个函数中。
我的问题是这样的: monad 的工作是因为它们强加了一个结构。坚持结构并遵守法律会带来乐趣和彩虹。然而 a -> s -> (b, s) 看起来并不像一元 a -> m[b]。
即使应用'a',它仍然是s -> (b, s)。区别在于后一个函数采用(单子?)STATE 并返回带有值的状态。而常规形式是:接受一个 VALUE 并返回一个 WRAPPED 值。
传入的参数以及返回类型的形式都会有所不同。然而许多文章都说这显然看起来像一个单子。对我来说肯定不是。
如果我要放松一下我被告知必须持有的观点:它不是将 m[a] 绑定到 a -> m[b],而是采用对 monad 有意义的任何参数并将其应用于任何函数签名有道理...然后我就可以看到它起作用了。
在这种情况下,我可以简单地说“哦,这个 monad 将 State[s] AND 'a' 绑定到像 a -> State[s] -> (State[s], b) 这样的函数”。然后它可以期望一个元组返回并将其解压到下一个函数的参数中。
但不知何故,我怀疑有一种方法可以使状态单子像所有其他单子一样工作 - 包括以某种方式期待 a -> m[b] 的形式(但它如何将状态贯穿其中?)。我怀疑这是可以做到的,因为我相信编写 Monad Transformers 将依赖于这些函数签名(形式?)的一致性。
即使您没有时间写出长篇大论的回复,从命令式程序员的角度来看一篇文章的链接也将是天赐之物。
(对于术语的任何错误使用,我深表歉意 - 我也在一路上学习这一点)
我认为正在讨论的 monad 实例确实保留了State
结构,并且元组在不应该的情况下让您陷入了循环。
如果我们退后一步并询问 Bind 应该具有什么签名才能符合我们的 monad 期望,我们会得到:
(>>=) :: State s a -> (a -> State s b) -> State s b
someState >>= someFuncOfA = ...
Run Code Online (Sandbox Code Playgroud)
我将在 Monad 值构造函数周围放置额外的括号,请记住,这不仅仅是State s
因为具体类型不能成为 Monad 的实例,而只能是一个参数的类型构造函数:State
(>>=) :: [State s] a -> (a -> [State s] b) -> [State s] b
someState >>= someFuncOfA = ...
Run Code Online (Sandbox Code Playgroud)
所有 monad 的东西中[State s]
我们的特别之处在哪里。m
由于新类型State
是
newtype State s a = State s -> (a, s)
Run Code Online (Sandbox Code Playgroud)
我们知道,无论值构造一个State
值意味着什么,该值所采用的参数都是一个函数 s -> (a, s)
。
所以备份我们不完整的绑定定义:
(>>=) :: [State s] a -> (a -> [State s] b) -> [State s] b
someState >>= someFuncOfA = ...
Run Code Online (Sandbox Code Playgroud)
我们知道这...
一定是State $ (someNewFunc)
因为这就是State
价值构建的方式。
第一个观察:元组与此没有太大关系。只要它的类型与值构造所需的类型相匹配,该函数就可以 维持我们所需的一元结构。该函数恰好需要具有类型这一事实几乎只是一种实现选择。签名可以是很多不同的东西,并且由于元组和值构造函数之间没有真正的区别,我确信您可以首先创建一个特殊类型,其值构造函数为此目的充当二元组并制作签名看起来人为地更好。someNewFunc
State
s -> (a, s)
data
第二个观察结果:State
没有导出'State'
值构造函数,至少没有 from Control.Monad.State
,因此您使用辅助函数
state :: (s -> (a, s)) -> State s a
Run Code Online (Sandbox Code Playgroud)
它再次将函数作为其第一个参数,并在幕后进行值构造。
所以我们知道在绑定定义内部,我们将无法编写
someState >>= someFuncOfA = State $ (someFunc)
Run Code Online (Sandbox Code Playgroud)
相反,它需要是
someState >>= someFuncOfA = state $ (someFunc)
Run Code Online (Sandbox Code Playgroud)
当state $ (someFunc)
评估时会导致类型,State s a
因为someFunc
被迫具有类型(s -> (a,s))
并且实际上会用来runState
完成此操作。
这可能不是一篇很好的文章。我尝试将FP Complete State Monad 教程的相关部分转化为解决这个特定问题的内容。也许阅读会提供更好的描述。