我假设,scalaz 7.0.x 和以下导入(查看 scalaz 6.x 的答案历史记录):
import scalaz._
import Scalaz._
状态类型定义为State[S, A],其中S 是状态类型,A 是被修饰值的类型。创建状态值的基本语法使用State[S, A] 函数:
// Create a state computation incrementing the state and returning the "str" value
val s = State[Int, String](i => (i + 1, "str"))
在初始值上运行状态计算:
// start with state of 1, pass it to s
s.eval(1)
// returns result value "str"
// same but only retrieve the state
s.exec(1)
// 2
// get both state and value
s(1) // or s.run(1)
// (2, "str")
状态可以通过函数调用线程化。要执行此操作而不是 Function[A, B],请定义 Function[A, State[S, B]]]。使用State函数...
import java.util.Random
def dice() = State[Random, Int](r => (r, r.nextInt(6) + 1))
那么for/yield语法可以用来组合函数:
def TwoDice() = for {
r1 <- dice()
r2 <- dice()
} yield (r1, r2)
// start with a known seed
TwoDice().eval(new Random(1L))
// resulting value is (Int, Int) = (4,5)
这是另一个例子。用TwoDice() 状态计算填充列表。
val list = List.fill(10)(TwoDice())
// List[scalaz.IndexedStateT[scalaz.Id.Id,Random,Random,(Int, Int)]]
使用序列获取State[Random, List[(Int,Int)]]。我们可以提供一个类型别名。
type StateRandom[x] = State[Random,x]
val list2 = list.sequence[StateRandom, (Int,Int)]
// list2: StateRandom[List[(Int, Int)]] = ...
// run this computation starting with state new Random(1L)
val tenDoubleThrows2 = list2.eval(new Random(1L))
// tenDoubleThrows2 : scalaz.Id.Id[List[(Int, Int)]] =
// List((4,5), (2,4), (3,5), (3,5), (5,5), (2,2), (2,4), (1,5), (3,1), (1,6))
或者我们可以使用sequenceU 来推断类型:
val list3 = list.sequenceU
val tenDoubleThrows3 = list3.eval(new Random(1L))
// tenDoubleThrows3 : scalaz.Id.Id[List[(Int, Int)]] =
// List((4,5), (2,4), (3,5), (3,5), (5,5), (2,2), (2,4), (1,5), (3,1), (1,6))
另一个使用State[Map[Int, Int], Int] 的示例来计算上面列表中总和的频率。 freqSum 计算投掷和计数频率的总和。
def freqSum(dice: (Int, Int)) = State[Map[Int,Int], Int]{ freq =>
val s = dice._1 + dice._2
val tuple = s -> (freq.getOrElse(s, 0) + 1)
(freq + tuple, s)
}
现在使用遍历将freqSum 应用于tenDoubleThrows。 traverse 等价于map(freqSum).sequence。
type StateFreq[x] = State[Map[Int,Int],x]
// only get the state
tenDoubleThrows2.copoint.traverse[StateFreq, Int](freqSum).exec(Map[Int,Int]())
// Map(10 -> 1, 6 -> 3, 9 -> 1, 7 -> 1, 8 -> 2, 4 -> 2) : scalaz.Id.Id[Map[Int,Int]]
或者更简洁地使用traverseU 来推断类型:
tenDoubleThrows2.copoint.traverseU(freqSum).exec(Map[Int,Int]())
// Map(10 -> 1, 6 -> 3, 9 -> 1, 7 -> 1, 8 -> 2, 4 -> 2) : scalaz.Id.Id[Map[Int,Int]]
请注意,因为State[S, A] 是StateT[Id, S, A] 的类型别名,所以tenDoubleThrows2 最终被键入为Id。我使用copoint 将其转回List 类型。
简而言之,使用状态的关键似乎是让函数返回一个修改状态的函数和所需的实际结果值...... 免责声明:我从未在生产代码中使用过state,只是在尝试感受一下。
@ziggystar 评论的其他信息
我放弃了尝试使用stateT 可能是其他人可以证明StateFreq 或StateRandom 是否可以增强以执行组合计算。相反,我发现两个状态转换器的组成可以这样组合:
def stateBicompose[S, T, A, B](
f: State[S, A],
g: (A) => State[T, B]) = State[(S,T), B]{ case (s, t) =>
val (newS, a) = f(s)
val (newT, b) = g(a) apply t
(newS, newT) -> b
}
它基于g 是一个单参数函数,它获取第一个状态转换器的结果并返回一个状态转换器。然后以下将起作用:
def diceAndFreqSum = stateBicompose(TwoDice, freqSum)
type St2[x] = State[(Random, Map[Int,Int]), x]
List.fill(10)(diceAndFreqSum).sequence[St2, Int].exec((new Random(1L), Map[Int,Int]()))