Estoy tratando de entender cómo reorganizar un programa que previamente habría escrito como una secuencia de transiciones de estado:
Tengo algo de lógica empresarial:
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
Con estos, deseo construir un programa para pasar algún estado inicial (un mapa vacío), leer la entrada de stdin , convertirla In
, ejecutar la transición de estado y generar el estado actual S
y la salida Out
a stdout .
Anteriormente, habría hecho algo como esto:
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)
Sin embargo, realmente estoy luchando por ver cómo conectar este enfoque (un flujo de transiciones de estado) con scalaz-stream . Empecé con esto:
type Transition = S => (S, Out)
val NoTransition: Transition = s => (s, 0)
io.stdInLines.map(inputToIn).map(_.fold(NoTransition)(transition))
Esto es de tipo: Process[Task, Transition]
. Realmente no sé a dónde ir desde allí.
- ¿Cómo "paso" mi programa
InitialState
y lo ejecuto, enhebrando la salidaS
en cada paso como entradaS
al siguiente? - ¿Cómo obtengo los valores de
S
yOut
en cada paso y los imprimo en stdout (asumiendo que puedo convertirlos en cadenas)?
Al tratar de usar un solo para la comprensión, me quedo atascado de manera similar:
for {
i <- Process.eval(Task.now(InitialState))
l <- io.stdInLines.map(inputToIn)
...
¡Cualquier ayuda es muy apreciada!
He llegado un poco más lejos ahora.
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))
Entonces
prog.run.run
No funciona: Parece que el estado no se enhebra a través del torrente. Más bien, en cada etapa, se pasa al estado inicial.
Paul Chiusano sugirió utilizar el enfoque de process1.scan
. Entonces ahora hago esto:
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))
Aquí hay un problema: en este ejemplo específico, mi Out
tipo es un monoide , por lo que mi estado inicial se puede crear usando su identidad, pero este no suele ser el caso. ¿Qué haría yo entonces? (Supongo que me vendría bien, Option
pero parece que no es necesario).