状态转换与无形状态monad

Sim*_*Sim 7 scala state-monad scalaz shapeless scalaz7

斯卡拉兹州立大学modify有以下签名:

def modify[S](f: S => S): State[S, Unit]
Run Code Online (Sandbox Code Playgroud)

这允许状态被相同类型的状态替换,当状态包括诸如Record其类型随着添加新字段而改变的无形值时,该状态不能很好地工作.在这种情况下,我们需要的是:

def modify[S, T](f: S => T): State[T, Unit]
Run Code Online (Sandbox Code Playgroud)

什么是使Scalaz的State monad适应无形状态的好方法,以便人们可以使用记录而不是可怕的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
Run Code Online (Sandbox Code Playgroud)

更新:

Travis答案的完整代码就在这里.

Tra*_*own 8

State是更通用类型的类型别名,IndexedStateT专门用于表示将状态类型更改为状态计算的函数:

type StateT[F[_], S, A] = IndexedStateT[F, S, S, A]
type State[S, A] = StateT[Id, S, A]
Run Code Online (Sandbox Code Playgroud)

虽然不可能编写你的modify[S, T]使用State,但是可以使用IndexedState(这是IndexedStateT修复效果类型的另一种类型别名Id):

import scalaz._, Scalaz._

def transform[S, T](f: S => T): IndexedState[S, T, Unit] =
  IndexedState(s => (f(s), ()))
Run Code Online (Sandbox Code Playgroud)

你甚至可以使用这种for理解(对我来说这似乎有点奇怪,因为monadic类型在操作之间发生了变化,但是它有效):

val s = for {
  a <- init[Int];
  _ <- transform[Int, Double](_.toDouble)
  _ <- transform[Double, String](_.toString)
  r <- get
} yield r * a
Run Code Online (Sandbox Code Playgroud)

然后:

scala> s(5)
res5: scalaz.Id.Id[(String, String)] = (5.0,5.05.05.05.05.0)
Run Code Online (Sandbox Code Playgroud)

在你的情况下你可能写这样的东西:

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), ()))
Run Code Online (Sandbox Code Playgroud)

然后:

def contrivedAdd[L <: HList](n: Int) = for {
  a <- init[S[L]]
  _ <- addField('latestAdded, n)(_ + n)
  r <- get
} yield r.total
Run Code Online (Sandbox Code Playgroud)

(这可能不是分解更新操作的最佳方法,但它显示了基本思想的工作原理.)

值得注意的是,如果你不关心将状态转换表示为状态计算,你可以使用imap任何旧的State:

init[S[HNil]].imap(s =>
  S(1, field[Witness.`'latestAdded`.T](1) :: s.scratch)
)
Run Code Online (Sandbox Code Playgroud)

这不允许您以相同的方式组合使用这些操作,但在某些情况下可能只需要它们.

  • 除了优雅之外,你的答案还包含一个整洁的小宝石,这是一个使用键和值扩展记录的`addField`.见证和`field [K](a)`的组合并不明显,无需阅读无形来源. (2认同)