从这个出色的tutorial中了解国家monad。但是,当我试图向非程序员解释时,他们有一个让我难过的问题。
如果State的目的是模拟可变内存,为什么状态monad存储的函数属于以下类型:
s -> (a, s)
而不仅仅是:
s -> s
换句话说,对“中间”价值的需求是什么?例如,在我们需要的情况下,我们不能通过简单地将状态定义为(state, value)
的元组来模拟它吗?
我确信我感到困惑,感谢任何帮助。
答案 0 :(得分:17)
为了与C等命令式语言并行绘制,s -> s
对应于返回类型为void
的函数,该函数仅用于副作用(例如改变内存)。它与State s ()
同构。
实际上,可以编写仅通过全局变量进行通信的C函数。但是,与在C中一样,从函数返回值通常很方便。这就是a
的用途。
当然,对于您的特定问题,s -> s
可能是更好的选择。虽然它不是Monad,但它是一个Monoid(当用Endo包裹时)。因此,您可以使用<>
和mempty
构建此类函数,这些函数对应于Monad的>>=
和return
。
答案 1 :(得分:5)
对尼克的答案进行扩展:
s
是州。如果您的所有函数都是s -> s
(状态到状态),那么您的函数将无法返回任何值。您可以将状态定义为(the actual state, value returned)
,但这会将状态与状态函数计算的值混为一谈。而且,通常情况下,您需要函数来实际计算和返回值...
答案 2 :(得分:2)
s' -> s'
相当于(a, s) -> (a, s)
。显而易见,除了State
之外,您的a
还需要初始s
才能启动。
另一方面,s -> (a, s)
只需要种子s
即可开始,并且根本不需要a
值。
因此s -> (a, s)
的类型告诉您State
不如(a, s) -> (a, s)
那么复杂。 Haskell中的类型传达了大量信息。
答案 3 :(得分:2)
如果
State
的目的是模拟可变内存,为什么状态monad存储的函数属于以下类型:s -> (a, s)
而不仅仅是:
s -> s
State
monad的目的不是模拟可变内存,而是模拟计算两者产生值和有副作用。简单地说,给定一些类型s
的初始状态,您的计算将产生类型a
的某个值,以及更新的状态。
也许你的计算不会产生一个值...然后,很简单:值类型a
只是()
。也许另一方面,你的计算没有副作用。同样,简单:您可能会认为您的状态转换函数(s -> s
参数modify
)只是id
。但通常你会同时处理这两个问题。
您实际上可以使用get
和put
作为相对简单的示例:
get :: State s s -- s -> (s, s)
put :: s -> State () -- s -> (s -> ((), s))
get
是一个计算,在给定当前状态(第一个s
)的情况下,它将作为一个值返回 - 即计算结果 - 和作为“新”(未修改)状态。
put
是一个计算,给定一个新状态(第一个s
)和一个当前状态(第二个s
),只需忽略< / em>当前状态。它将产生()
作为计算值(因为,当然,它没有计算任何值!)并挂起提供的新状态。
答案 4 :(得分:1)
据推测,您希望在do
符号内使用有状态计算?
你应该问问自己Monad
实例对于由
newtype State s = { runState :: s -> s }
答案 5 :(得分:0)
答案 6 :(得分:0)
要解决的问题是您有一个输入和一系列函数,并且您希望按顺序将这些函数应用于输入。
如果函数是纯粹的状态更改函数,s -> s
类型为s
的输入,那么您不需要 State
来使用它们。 Haskell非常擅长将这些函数链接在一起,例如:使用标准合成运算符.
,或类似foldr (.) id
或foldr id
。
但是,如果这些函数都改变了一个状态和报告了一些这样做的结果,那么你可以给它们类型s -> (s,a)
,然后将它们粘合在一起就是一点点令人讨厌的。您必须解压缩结果元组并将新状态值传递给下一个函数,在其他地方使用报告的值,然后解压那个结果,依此类推。将错误的状态传递给输入函数很容易,因为您必须为每个结果命名并明确输入以进行解包。你最终得到这样的东西:
let
(res1, s1) = fun1 s0
(res2, s2) = fun2 s1
(res3, s3) = fun3 res1 res2 s1
...
in resN
在那里,我不小心通过了s1
而不是s2
,可能是因为我后来添加了第二行,并没有意识到第三行需要更改。在编写s -> s
函数时,不可能出现此问题,因为没有名称可以正确使用:
let
resN = fun1 . fun2 . fun3 . -- etc.
所以我们发明了State
来做同样的伎俩。 State
基本上只是将s -> (s,a)
之类的函数粘合在一起,以便将正确的状态传递给正确的函数。
所以人们去的并不是“我们想要使用State
,让我们使用s -> (s,a)
”而是“我们正在编写像s -> (s,a)
这样的函数,让我们发明{{1使这很容易“。使用函数State
,它已经很简单了,我们不需要发明任何东西。
作为s -> s
如何自然产生的一个例子,考虑解析:解析器将被赋予一些输入,消耗一些输入并返回一个值。在Haskell中,这自然被建模为获取输入列表,并返回一对值和剩余输入 - 即s -> (s,a)
或[Input] -> ([Input], a)
。