Ich versuche zu verstehen, wie man ein Programm reorganisiert, das ich zuvor als Folge von Zustandsübergängen geschrieben hätte:
Ich habe eine Geschäftslogik:
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
Mit diesen möchte ich ein Programm erstellen, das in einem Anfangszustand (einer leeren Karte) übergeben wird, Eingaben von stdin liest , in konvertiert In
, den Zustandsübergang ausführt und den aktuellen Zustand S
und die Ausgabe Out
in stdout ausgibt .
Zuvor hätte ich so etwas gemacht:
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)
Ich kämpfe jedoch wirklich darum, wie ich diesen Ansatz (einen Strom von Zustandsübergängen) mit Scalaz-Stream verbinden kann . Ich habe damit angefangen:
type Transition = S => (S, Out)
val NoTransition: Transition = s => (s, 0)
io.stdInLines.map(inputToIn).map(_.fold(NoTransition)(transition))
Dies ist vom Typ : Process[Task, Transition]
. Ich weiß nicht wirklich, wohin ich von dort aus gehen soll.
- Wie "gebe" ich meine
InitialState
und führe das Programm aus, wobei die AusgabeS
bei jedem Schritt als EingabeS
für die nächste einfädle? - Wie erhalte ich die Werte von
S
undOut
bei jedem Schritt und drucke sie aus stdout (vorausgesetzt, ich kann sie in Zeichenfolgen konvertieren)?
Beim Versuch, ein einziges Verständnis zu verwenden, stecke ich ähnlich fest:
for {
i <- Process.eval(Task.now(InitialState))
l <- io.stdInLines.map(inputToIn)
...
Jede Hilfe wird sehr geschätzt!
Ich bin jetzt ein bisschen weiter.
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))
Dann
prog.run.run
Es funktioniert nicht: Es scheint, als würde der Status nicht eingefädelt durch den Stream . Vielmehr wird in jeder Phase der Ausgangszustand übergeben.
Paul Chiusano schlug vor, den Ansatz von zu verwenden process1.scan
. Also jetzt mache ich das:
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))
Hier gibt es ein Problem: In diesem speziellen Beispiel ist mein Out
Typ ein Monoid , sodass mein Anfangszustand anhand seiner Identität erstellt werden kann , dies ist jedoch im Allgemeinen möglicherweise nicht der Fall. Was würde ich dann tun? (Ich denke, ich könnte es verwenden, Option
aber das scheint unnötig zu sein.)