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 Sund die Ausgabe Outin 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
InitialStateund führe das Programm aus, wobei die AusgabeSbei jedem Schritt als EingabeSfür die nächste einfädle? - Wie erhalte ich die Werte von
SundOutbei 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 OutTyp 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, Optionaber das scheint unnötig zu sein.)