我试图了解如何重组程序,该程序以前是作为状态转换序列编写的:
我有一些业务逻辑:
type In = Long
type Count = Int
type Out = Count
type S = Map[Int, Count]
val inputToIn: String => Option[In]
= s => try Some(s.toLong) catch { case _ : Throwable => None }
def transition(in: In): S => (S, Out)
= s => { val n = s.getOrElse(in, 0); (s + (in -> n+1), n+1) }
val ZeroOut: Out = 0
val InitialState: S = Map.empty
有了这些,我希望构建一个传递一些初始状态(空Map)的程序,从stdin读取输入,将其转换为In
,运行状态转换,并将当前状态S
输出Out
,并输出到stdout。
以前,我会做这样的事情:
val runOnce = StateT[IO, S, Out](s => IO.readLn.map(inputToIn) flatMap {
case None => IO((s, ZeroOut))
case Some(in) => val (t, o) = transition(in)(s)
IO.putStrLn(t.toString) |+| IO.putStrLn(o.toString) >| IO((t, o))
})
Stream.continually(runOnce).sequenceU.eval(InitialState)
但是,我真的很想知道如何将这种方法(状态转换流)与scalaz-stream连接起来。我从这个开始:
type Transition = S => (S, Out)
val NoTransition: Transition = s => (s, 0)
io.stdInLines.map(inputToIn).map(_.fold(NoTransition)(transition))
这是类型:Process[Task, Transition]
。我真的不知道从那里去。
- 如何“传入”我
InitialState
的程序并运行,S
在每一步中将输出作为S
下一个输入的线程? - 我如何获得的值
S
,并Out
在每一步,并将其打印到标准输出(假设我可以将它们转换为字符串)?
在尝试使用单个理解时,我同样遇到了困难:
for {
i <- Process.eval(Task.now(InitialState))
l <- io.stdInLines.map(inputToIn)
...
任何帮助是极大的赞赏!
我现在还有一点。
type In_ = (S, Option[In])
type Out_ = (S, Out)
val input: Process[Task, In_]
= for {
i <- Process.emit(InitialState)
o <- io.stdInLines.map(inputToIn)
} yield (i, o)
val prog =
input.pipe(process1.collect[In_, Out_]) {
case (s, Some(in)) => transition(in)(s)
}).to(io.stdOutLines.contramap[Out_](_.toString))
然后
prog.run.run
它不工作:好像状态不是被拧过流。而是在每个阶段都传递初始状态。
保罗·基萨诺(Paul Chiusano)建议使用的方法process1.scan
。所以现在我这样做:
type In_ = In
type Out_ = (S, Out)
val InitialOut_ = (InitialState, ZeroOut)
val program =
io.stdInLines.collect(Function.unlift(inputToIn)).pipe(
process1.scan[In_, Out_](InitialOut_) {
case ((s, _), in) => transition(in)(s)
}).to(io.stdOutLines.contramap[Out_](_.shows))
这里存在一个问题:在此特定示例中,我的Out
类型是monoid,因此可以使用其身份创建我的初始状态,但通常情况并非如此。那我该怎么办?(我想我可以使用,Option
但这似乎是不必要的。)