使用无形状态 Monad 的状态转换
State transformations with a shapeless State monad
Scalaz State monad 的 modify
具有以下签名:
def modify[S](f: S => S): State[S, Unit]
这允许用相同类型的状态替换状态,当状态包含无形值(例如 Record
其类型随着新字段的添加而改变时,这种方法效果不佳。在那种情况下,我们需要的是:
def modify[S, T](f: S => T): State[T, Unit]
什么是使 Scalaz 的 State monad 适应使用无形状态的好方法,以便可以使用 Records 而不是可怕的 Map[String, Any]
?
示例:
case class S[L <: HList](total: Int, scratch: L)
def contrivedAdd[L <: HList](n: Int): State[S[L], Int] =
for {
a <- init
_ <- modify(s => S(s.total + n, ('latestAddend ->> n) :: s.scratch))
r <- get
} yield r.total
更新:
Travis 答案的完整代码是 here。
State
是更通用类型 IndexedStateT
的类型别名,它专门用于表示将状态类型更改为状态计算的函数:
type StateT[F[_], S, A] = IndexedStateT[F, S, S, A]
type State[S, A] = StateT[Id, S, A]
虽然无法使用 State
编写 modify[S, T]
,但可以使用 IndexedState
(这是 IndexedStateT
的另一种类型别名,可修复效果类型至 Id
):
import scalaz._, Scalaz._
def transform[S, T](f: S => T): IndexedState[S, T, Unit] =
IndexedState(s => (f(s), ()))
你甚至可以在 for
-comprehensions 中使用它(这对我来说总是有点奇怪,因为 monadic 类型在操作之间改变,但它有效):
val s = for {
a <- init[Int];
_ <- transform[Int, Double](_.toDouble)
_ <- transform[Double, String](_.toString)
r <- get
} yield r * a
然后:
scala> s(5)
res5: scalaz.Id.Id[(String, String)] = (5.0,5.05.05.05.05.0)
在你的情况下你可以这样写:
import shapeless._, shapeless.labelled.{ FieldType, field }
case class S[L <: HList](total: Int, scratch: L)
def addField[K <: Symbol, A, L <: HList](k: Witness.Aux[K], a: A)(
f: Int => Int
): IndexedState[S[L], S[FieldType[K, A] :: L], Unit] =
IndexedState(s => (S(f(s.total), field[K](a) :: s.scratch), ()))
然后:
def contrivedAdd[L <: HList](n: Int) = for {
a <- init[S[L]]
_ <- addField('latestAdded, n)(_ + n)
r <- get
} yield r.total
(这可能不是分解更新操作的最佳方式,但它展示了基本思想是如何工作的。)
同样值得注意的是,如果您不关心将状态转换表示为状态计算,您可以在任何旧的 State
:
上使用 imap
init[S[HNil]].imap(s =>
S(1, field[Witness.`'latestAdded`.T](1) :: s.scratch)
)
这不允许您以相同的方式组合使用这些操作,但在某些情况下它可能就是您所需要的。
Scalaz State monad 的 modify
具有以下签名:
def modify[S](f: S => S): State[S, Unit]
这允许用相同类型的状态替换状态,当状态包含无形值(例如 Record
其类型随着新字段的添加而改变时,这种方法效果不佳。在那种情况下,我们需要的是:
def modify[S, T](f: S => T): State[T, Unit]
什么是使 Scalaz 的 State monad 适应使用无形状态的好方法,以便可以使用 Records 而不是可怕的 Map[String, Any]
?
示例:
case class S[L <: HList](total: Int, scratch: L)
def contrivedAdd[L <: HList](n: Int): State[S[L], Int] =
for {
a <- init
_ <- modify(s => S(s.total + n, ('latestAddend ->> n) :: s.scratch))
r <- get
} yield r.total
更新:
Travis 答案的完整代码是 here。
State
是更通用类型 IndexedStateT
的类型别名,它专门用于表示将状态类型更改为状态计算的函数:
type StateT[F[_], S, A] = IndexedStateT[F, S, S, A]
type State[S, A] = StateT[Id, S, A]
虽然无法使用 State
编写 modify[S, T]
,但可以使用 IndexedState
(这是 IndexedStateT
的另一种类型别名,可修复效果类型至 Id
):
import scalaz._, Scalaz._
def transform[S, T](f: S => T): IndexedState[S, T, Unit] =
IndexedState(s => (f(s), ()))
你甚至可以在 for
-comprehensions 中使用它(这对我来说总是有点奇怪,因为 monadic 类型在操作之间改变,但它有效):
val s = for {
a <- init[Int];
_ <- transform[Int, Double](_.toDouble)
_ <- transform[Double, String](_.toString)
r <- get
} yield r * a
然后:
scala> s(5)
res5: scalaz.Id.Id[(String, String)] = (5.0,5.05.05.05.05.0)
在你的情况下你可以这样写:
import shapeless._, shapeless.labelled.{ FieldType, field }
case class S[L <: HList](total: Int, scratch: L)
def addField[K <: Symbol, A, L <: HList](k: Witness.Aux[K], a: A)(
f: Int => Int
): IndexedState[S[L], S[FieldType[K, A] :: L], Unit] =
IndexedState(s => (S(f(s.total), field[K](a) :: s.scratch), ()))
然后:
def contrivedAdd[L <: HList](n: Int) = for {
a <- init[S[L]]
_ <- addField('latestAdded, n)(_ + n)
r <- get
} yield r.total
(这可能不是分解更新操作的最佳方式,但它展示了基本思想是如何工作的。)
同样值得注意的是,如果您不关心将状态转换表示为状态计算,您可以在任何旧的 State
:
imap
init[S[HNil]].imap(s =>
S(1, field[Witness.`'latestAdded`.T](1) :: s.scratch)
)
这不允许您以相同的方式组合使用这些操作,但在某些情况下它可能就是您所需要的。