为什么国家需要一个价值?

时间:2012-07-20 16:34:32

标签: haskell monads

从这个出色的tutorial中了解国家monad。但是,当我试图向非程序员解释时,他们有一个让我难过的问题。

如果State的目的是模拟可变内存,为什么状态monad存储的函数属于以下类型:

s -> (a, s)

而不仅仅是:

s -> s

换句话说,对“中间”价值的需求是什么?例如,在我们需要的情况下,我们不能通过简单地将状态定义为(state, value)的元组来模拟它吗?

我确信我感到困惑,感谢任何帮助。

7 个答案:

答案 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。但通常你会同时处理这两个问题。


您实际上可以使用getput作为相对简单的示例:

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)

a是返回的值,s是最终状态。

http://www.haskell.org/haskellwiki/State_Monad#Implementation

答案 6 :(得分:0)

要解决的问题是您有一个输入和一系列函数,并且您希望按顺序将这些函数应用于输入。

如果函数是纯粹的状态更改函数,s -> s类型为s的输入,那么您不需要 State来使用它们。 Haskell非常擅长将这些函数链接在一起,例如:使用标准合成运算符.,或类似foldr (.) idfoldr 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)