当我了解State Monad
时,我不确定如何使用不同的State
返回类型编写两个函数。
State Monad定义:
case class State[S, A](runState: S => (S, A)) {
def flatMap[B](f: A => State[S, B]): State[S, B] = {
State(s => {
val (s1, a) = runState(s)
val (s2, b) = f(a).runState(s1)
(s2, b)
})
}
def map[B](f: A => B): State[S, B] = {
flatMap(a => {
State(s => (s, f(a)))
})
}
}
两种不同的州类型:
type AppendBang[A] = State[Int, A]
type AddOne[A] = State[String, A]
两种具有不同状态返回类型的方法:
def addOne(n: Int): AddOne[Int] = State(s => (s + ".", n + 1))
def appendBang(str: String): AppendBang[String] = State(s => (s + 1, str + " !!!"))
定义一个函数以使用上述两个函数:
def myAction(n: Int) = for {
a <- addOne(n)
b <- appendBang(a.toString)
} yield (a, b)
我希望像这样使用它:
println(myAction(1))
问题是myAction
无法编译,它报告了一些错误:
Error:(14, 7) type mismatch;
found : state_monad.State[Int,(Int, String)]
required: state_monad.State[String,?]
b <- appendBang(a.toString)
^
我该如何解决?我是否必须定义一些Monad变换器?
更新:问题可能不明确,让我举个例子
我想要定义另一个在内部使用addOne
和appendBang
的函数。既然他们都需要现有的状态,我必须传递一些:
def myAction(n: Int)(addOneState: String, appendBangState: Int): ((String, Int), String) = {
val (addOneState2, n2) = addOne(n).runState(addOneState)
val (appendBangState2, n3) = appendBang(n2.toString).runState(appendBangState)
((addOneState2, appendBangState2), n3)
}
我必须逐个运行addOne
和appendBang
,手动传递和获取状态和结果。
虽然我发现它可以返回另一个State
,但代码没有太大改进:
def myAction(n: Int): State[(String, Int), String] = State {
case (addOneState: String, appendBangState: Int) =>
val (addOneState2, n2) = addOne(n).runState(addOneState)
val (appendBangState2, n3) = appendBang(n2.toString).runState( appendBangState)
((addOneState2, appendBangState2), n3)
}
由于我不熟悉它们,只是想知道有没有办法改进它。最好的希望是我可以使用for
理解,但不确定是否可能
答案 0 :(得分:4)
就像我在第一篇评论中提到的那样,使用理解来做你想做的事是不可能的,因为它不能改变状态的类型(S
)。
请记住,理解可以转换为flatMaps
,withFilter
和一个map
的组合。如果我们查看您的State.flatMap
,则需要一个函数f
才能将State[S,A]
更改为State[S, B]
。我们可以使用flatMap
和map
(以便进行理解)将操作链接到同一个状态,但我们无法更改此链中的状态类型。
我们可以概括你的myAction
的最后一个定义,以使用不同类型的状态组合,组合......两个函数。我们可以尝试直接在我们的State
类中实现这种通用的compose方法(尽管这可能是如此具体,它可能不属于State
)。如果我们查看State.flatMap
和myAction
,我们可以看到一些相似之处:
runState
实例上致电State
。runState
在myAction
中,我们首先使用结果n2
使用第二个函数(State[Int, String]
创建AppendBang[String]
(State[S2, B]
或appendBang
)或f
),然后我们称之为runState
。但我们的结果n2
的类型为String
(A
),而我们的函数appendBang
需要Int
(B
),因此我们需要一个函数将A
转换为B
。
case class State[S, A](runState: S => (S, A)) {
// flatMap and map
def compose[B, S2](f: B => State[S2, B], convert: A => B) : State[(S, S2), B] =
State( ((s: S, s2: S2) => {
val (sNext, a) = runState(s)
val (s2Next, b) = f(convert(a)).runState(s2)
((sNext, s2Next), b)
}).tupled)
}
然后,您可以将myAction
定义为:
def myAction(i: Int) = addOne(i).compose(appendBang, _.toString)
val twoStates = myAction(1)
// State[(String, Int),String] = State(<function1>)
twoStates.runState(("", 1))
// ((String, Int), String) = ((.,2),2 !!!)
如果您不想在State
课程中使用此功能,可以将其创建为外部功能:
def combineStateFunctions[S1, S2, A, B](
a: A => State[S1, A],
b: B => State[S2, B],
convert: A => B
)(input: A): State[(S1, S2), B] = State(
((s1: S1, s2: S2) => {
val (s1Next, temp) = a(input).runState(s1)
val (s2Next, result) = b(convert(temp)).runState(s2)
((s1Next, s2Next), result)
}).tupled
)
def myAction(i: Int) =
combineStateFunctions(addOne, appendBang, (_: Int).toString)(i)
修改: Bergi 创建两个函数,将State[A, X]
或State[B, X]
提升为State[(A, B), X]
。
object State {
def onFirst[A, B, X](s: State[A, X]): State[(A, B), X] = {
val runState = (a: A, b: B) => {
val (nextA, x) = s.runState(a)
((nextA, b), x)
}
State(runState.tupled)
}
def onSecond[A, B, X](s: State[B, X]): State[(A, B), X] = {
val runState = (a: A, b: B) => {
val (nextB, x) = s.runState(b)
((a, nextB), x)
}
State(runState.tupled)
}
}
这样你可以使用for comprehension,因为状态的类型保持不变((A, B)
)。
def myAction(i: Int) = for {
x <- State.onFirst(addOne(i))
y <- State.onSecond(appendBang(x.toString))
} yield y
myAction(1).runState(("", 1))
// ((String, Int), String) = ((.,2),2 !!!)