Próbuję zrozumieć, jak zreorganizować program, który wcześniej napisałbym jako sekwencję przejść stanów:
Mam logikę biznesową:
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
Dzięki nim chcę skonstruować program do przekazania w jakimś początkowym stanie (pusta mapa), odczytać dane wejściowe ze standardowego wejścia , przekonwertować je na In
, uruchomić przejście stanu i wyprowadzić stan bieżący S
i wyjście Out
na standardowe wyjście .
Wcześniej zrobiłbym coś takiego:
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)
Jednak naprawdę staram się zobaczyć, jak połączyć to podejście (strumień przejść stanów) ze scalaz-stream . Zacząłem od tego:
type Transition = S => (S, Out)
val NoTransition: Transition = s => (s, 0)
io.stdInLines.map(inputToIn).map(_.fold(NoTransition)(transition))
To jest typu: Process[Task, Transition]
. Naprawdę nie wiem, dokąd mam iść.
- Jak „przekazać” mój
InitialState
i uruchomić program, łącząc dane wyjścioweS
w każdym kroku jako dane wejścioweS
do następnego? - Jak uzyskać wartości
S
iOut
na każdym kroku i wydrukować je na standardowe wyjście (zakładając, że mogę je przekonwertować na ciągi)?
Próbując użyć pojedynczego dla zrozumienia, utknąłem w podobny sposób:
for {
i <- Process.eval(Task.now(InitialState))
l <- io.stdInLines.map(inputToIn)
...
Każda pomoc jest mile widziana!
Teraz poszedłem trochę dalej.
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))
Następnie
prog.run.run
To nie działa: Wygląda na to, że państwo nie jest gwintowany przez strumień. Raczej na każdym etapie przekazywany jest stan początkowy.
Paul Chiusano zasugerował zastosowanie podejścia process1.scan
. Więc teraz robię to:
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))
Jest tu problem: w tym konkretnym przykładzie mój Out
typ jest monoidem , więc mój stan początkowy można utworzyć za pomocą jego tożsamości, ale generalnie może tak nie być. Co bym wtedy zrobił? (Myślę, że mógłbym użyć, Option
ale wydaje się, że jest to niepotrzebne).