如何组成两个不同的`State Monad`?
How to compose two different `State Monad`?
当我学习 State Monad
时,我不确定如何组合具有不同 State
return 类型的两个函数。
状态 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]
具有不同状态 return 类型的两种方法:
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
一个接一个,手动传递和获取状态和结果。
虽然我发现可以return另一个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
理解,但不确定是否可行
就像我在第一条评论中提到的那样,不可能使用 for 理解来做你想做的事,因为它不能改变状态的类型 (S
)。
请记住,for comprehension 可以翻译为 flatMaps
、withFilter
和一个 map
的组合。如果我们查看您的 State.flatMap
,它需要一个函数 f
才能将 State[S,A]
更改为 State[S, B]
。我们可以使用 flatMap
和 map
(因此是 for comprehension)将对同一状态的操作链接在一起,但我们不能更改此链中的状态类型。
我们可以概括您对 myAction
的最后定义,以使用不同类型的状态组合、组合...两个函数。我们可以尝试在我们的 State
class 中直接实现这个通用的 compose 方法(虽然这可能非常具体,但它可能不属于 State
)。如果我们查看 State.flatMap
和 myAction
,我们可以看到一些相似之处:
- 我们首先在现有的
State
实例上调用 runState
。
- 然后我们再次调用
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
class 中使用此函数,您可以将其创建为外部函数:
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 !!!)
当我学习 State Monad
时,我不确定如何组合具有不同 State
return 类型的两个函数。
状态 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]
具有不同状态 return 类型的两种方法:
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
一个接一个,手动传递和获取状态和结果。
虽然我发现可以return另一个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
理解,但不确定是否可行
就像我在第一条评论中提到的那样,不可能使用 for 理解来做你想做的事,因为它不能改变状态的类型 (S
)。
请记住,for comprehension 可以翻译为 flatMaps
、withFilter
和一个 map
的组合。如果我们查看您的 State.flatMap
,它需要一个函数 f
才能将 State[S,A]
更改为 State[S, B]
。我们可以使用 flatMap
和 map
(因此是 for comprehension)将对同一状态的操作链接在一起,但我们不能更改此链中的状态类型。
我们可以概括您对 myAction
的最后定义,以使用不同类型的状态组合、组合...两个函数。我们可以尝试在我们的 State
class 中直接实现这个通用的 compose 方法(虽然这可能非常具体,但它可能不属于 State
)。如果我们查看 State.flatMap
和 myAction
,我们可以看到一些相似之处:
- 我们首先在现有的
State
实例上调用runState
。 - 然后我们再次调用
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
class 中使用此函数,您可以将其创建为外部函数:
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 !!!)