Scala和State Monad

时间:2015-12-22 01:40:18

标签: scala state-monad

我一直在努力了解State Monad。与其如何使用不同,尽管这并不总是很容易找到。但是我发现State Monad的每一次讨论基本上都有相同的信息,总有一些我不理解的事情。

this帖子为例。其中作者有以下内容:

case class State[S, A](run: S => (A, S)) {
...
  def flatMap[B](f: A => State[S, B]): State[S, B] =
    State(s => {
      val (a, t) = run(s)
      f(a) run t
    })
...
}

我可以看到这些类型正确排列。但是,我根本不理解第二个run

也许我正在考虑这个monad的整个目的。我从HaskellWiki那里得到的印象是,状态monad有点像状态机,run允许转换(但是,在这种情况下,状态机并没有真正具有固定状态转换像大多数国家机器一样)。如果是这种情况,那么上面的代码(a, t)将表示单个转换。 f的应用程序将代表该值的修改和State(生成新的State对象)。这让我对第二个run的全部内容感到困惑。这似乎是第二次过渡'。但这对我没有任何意义。

我可以看到在生成的run对象上调用State会产生一个新的(A, S)对,当然,这些类型需要排列。但我真的不明白这应该是做什么的。

那么,这里到底发生了什么?在这里建模的概念是什么?

编辑:2015年12月22日

所以,看来我并没有很好地表达我的问题。让我试一试。

在同一篇博文中,我们看到了map的以下代码:

def map[B](f: A => B): State[S, B] =
  State(s => {
    val (a, t) = run(s)
    (f(a), t)
  })

显然,此处只有一次run来电。

我一直试图调和的模型是,对run的调用会移动我们通过单个状态变化保持前进的状态。这似乎是map中的情况。但是,在flatMap中,我们有两次调用run。如果我的模型是正确的,那将导致跳过'国家改变。

要使用下面提供的示例@Filppo,第一次调用run将导致返回(1, List(2,3,4,5)),第二次调用将导致(2, List(3,4,5)),从而有效地跳过第一个map 。因为,在他的示例中,紧接着是(Map(a->2, b->3), List(4,5))的调用,这将导致scala> val v = State(head[Int]).flatMap { a => State(head[Int]) } v: State[List[Int],Int] = State(<function1> scala> v.run(List(1,2,3,4,5)) res2: (Int, List[Int]) = (2,List(3, 4, 5))

显然这不是正在发生的事情。所以我的整个模型是不正确的。推理这个的正确方法是什么?

第二次编辑:2015年12月22日

我刚刚尝试按照我在REPL中所说的做法。我的直觉是正确的,这让我更加困惑。

flatMap

因此,for(bool isTypeValid=false; isTypeValid==false;){ cout<<"type: "; cin>>type; switch(type){ case 'a': case 'b': case 'c': isTypeValid=true; break; default: isTypeValid=false; cout<<"Error! Invalid type. Try again."; break;} } int number: cout<<"number: "; cin>>number; ............. 的此实施会跳过某个州。然而,当我运行@Filippo的例子时,我得到了同样的答案。这里真的发生了什么?

3 个答案:

答案 0 :(得分:8)

了解&#34;第二次运行&#34;让我们分析它&#34;向后&#34;。

签名def flatMap[B](f: A => State[S, B]): State[S, B]表明我们需要运行函数f并返回其结果。

要执行功能f,我们需要给它一个A。我们从哪里得到一个? 好吧,我们run可以从A中提取S,因此我们需要S

因此我们这样做:s => val (a, t) = run(s) ...。 我们将其视为&#34;给定S执行生成我们run的{​​{1}}函数和新A。这是我们的第一个&#34;运行

现在我们有S,我们可以执行A。这就是我们想要的,f给了我们一个新的f(a)。 如果我们这样做,那么我们有一个函数,它需要State[S, B]并返回S

Stats[S, B]

但是功能(s: S) => val (a, t) = run(s) f(a) //State[S, B] 并不是我们想要归还的东西!我们只想返回S => State[S, B]

我们如何做到这一点?我们可以将此函数包装到State[S, B]

State

但它不起作用,因为State(s => ... f(a)) 需要State,而不是S => (B, S)。 因此,我们需要从S => State[B, S]中获取(B, S) 我们通过调用它的State[B, S]方法并为它提供我们刚才在上一步生成的状态来实现它! 这是我们的第二个&#34;运行

因此,我们对run执行了以下转换:

flatMap

这给了我们s => // when a state is provided val (a, t) = run(s) // produce an `A` and a new state value val resState = f(a) // produce a new `State[S, B]` resState.run(t) // return `(S, B)` ,我们只用S => (S, B)构造函数包装它。

另一种看待这些&#34;两次跑步的方式&#34;是:
首先 - 我们用#34;我们的&#34; State功能
second - 我们将转换后的状态传递给函数run并让它自己进行转换。

所以我们有点&#34;链接&#34;状态转变一个接一个。这正是monad所做的:它们为我们提供了按顺序安排计算的能力。

答案 1 :(得分:4)

state monad归结为此功能,从state转换为另一个state(加A):

type StatefulComputation[S, +A] = S => (A, S)

Tony在该博客中提到的实现将“捕获”功能“捕获”到run的{​​{1}}中:

case class

case class State[S, A](run: S => (A, S)) flatmap bind state到另一个state的实施正在调用2个不同的run

    // the `run` on the actual `state`
    val (a: A, nextState: S) = run(s)

    // the `run` on the bound `state`
    f(a).run(nextState)

编辑 flatmap之间的State示例

考虑将.head简单地调用List以获取A,将.tail调用为下一个状态S

的功能
// stateful computation: `S => (A, S)` where `S` is `List[A]`
def head[A](xs: List[A]): (A, List[A]) = (xs.head, xs.tail)

2 State(head[Int])的简单绑定:

// flatmap example
val result = for {
  a <- State(head[Int])
  b <- State(head[Int])
} yield Map('a' -> a,
            'b' -> b)

for-comprehension的期望行为是将列表的第一个元素“提取”为a,将b中的第二个元素“提取”。结果状态S将是运行列表的剩余尾部:

scala> result.run(List(1, 2, 3, 4, 5))
(Map(a -> 1, b -> 2),List(3, 4, 5))

如何?在某个州head[Int]上调用run中的“有状态计算”s

s => run(s)

这会给出列表的headA)和tailB)。现在我们需要将tail传递给下一个State(head[Int])

f(a).run(t)

f签名中flatmap的位置:

def flatMap[B](f: A => State[S, B]): State[S, B]

也许为了更好地理解这个例子中f的内容,我们应该将for-comprehension去糖化为:

val result = State(head[Int]).flatMap {
  a => State(head[Int]).map {
    b => Map('a' -> a, 'b' -> b)
  }
}

使用f(a)我们将a传递给函数,并使用run(t)传递修改后的状态。

答案 2 :(得分:2)

我接受了@ AlexyRaga对我的问题的回答。我认为@ Filippo的回答非常好,事实上,给了我一些额外的思考。谢谢你们两个。

我认为我遇到的概念上的困难主要与“run方法意味着什么”有关。也就是说,它的目的和结果是什么。我把它看作是一个“过渡”功能(从一个州到下一个州)。而且,经过时尚之后,就是这样。但是,它不会从给定(this)状态转换到下一个状态。相反,它需要一个初始State并返回(this)状态的值和一个新的“当前”状态(而不是状态转换序列中的下一个状态)。

这就是flatMap方法按原样实现的原因。当您生成新的State时,您需要基于传入的初始状态从中获取当前值/状态对,然后可以将其作为函数包装在新的State对象中。你并没有真正过渡到一个新的国家。只需将生成的状态重新包装在新的State对象中。

我过于沉浸在传统的状态机中,看看这里发生了什么。

再次感谢大家。