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.)
quelle
StateT
Konstrukt wie die Trägermonade für Ihren Stream verwenden?type Carrier[A] = StateT[Task, S, A]; val input: Process[Carrier, Option[In]] = ...; prog.run.run(initialValue).run // prog.run is a Carrier[Unit] i.e. StateT
Antworten:
quelle