我一直在努力了解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的例子时,我得到了同样的答案。这里真的发生了什么?
答案 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)
这会给出列表的head
(A
)和tail
(B
)。现在我们需要将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
对象中。
我过于沉浸在传统的状态机中,看看这里发生了什么。
再次感谢大家。