[scala] 시퀀스 된 상태 전환 스트림으로 작성된 프로그램을 scalaz-stream으로 어떻게 대체합니까?

이전에 일련의 상태 전환으로 작성했을 프로그램을 재구성하는 방법을 이해하려고합니다.

몇 가지 비즈니스 논리가 있습니다.

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

이것들을 사용하여 초기 State (빈 Map)를 전달하고 stdin 에서 입력을 읽고으로 변환 In하고 상태 전환을 실행하고 현재 상태 S와 출력 Outstdout으로 출력 하는 프로그램을 구성하고 싶습니다 .


이전에는 다음과 같이했을 것입니다.

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)

그러나 저는이 접근 방식 (상태 전환 스트림)을 scalaz-stream 과 연결하는 방법을보기 위해 정말 고심하고 있습니다 . 나는 이것을 시작했다.

type Transition = S => (S, Out)
val NoTransition: Transition = s => (s, 0)

io.stdInLines.map(inputToIn).map(_.fold(NoTransition)(transition))

유형 : Process[Task, Transition]. 나는 거기에서 어디로 가야할지 정말로 모른다.

  1. 어떻게 “전달” InitialState하고 프로그램을 실행하여 S각 단계 의 출력 을 S다음 단계의 입력 으로 스레딩 합니까?
  2. 각 단계에서 S및 의 값을 가져 Out와서 stdout으로 인쇄 하려면 어떻게해야합니까 (문자열로 변환 할 수 있다고 가정)?

단일 이해력을 사용하려고 할 때 비슷한 문제가 발생합니다.

for {
  i <- Process.eval(Task.now(InitialState))
  l <- io.stdInLines.map(inputToIn)
...

어떤 도움이라도 대단히 감사합니다!


이제 조금 더 나아갔습니다.

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))

그때

prog.run.run

작동하지 않습니다. 상태가 스트림을 통해 스레드 되지 않는 것 같습니다 . 오히려 각 단계에서 초기 상태가 전달됩니다.


Paul Chiusano는 process1.scan. 이제 이렇게합니다.

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))

여기에 문제가 있습니다.이 특정 예제에서 내 Out유형은 monoid 이므로 ID를 사용하여 초기 상태를 만들 수 있지만 일반적으로 그렇지 않을 수 있습니다. 그럼 내가 뭘할까요? (사용할 수있을 Option것 같지만 불필요한 것 같습니다.)



답변

import io.FilePath

import scalaz.stream._
import Process._
import scalaz.concurrent.Task
import Task._
import scalaz.{Show, Reducer, Monoid}
import scalaz.std.list._
import scalaz.syntax.foldable._
import scalaz.syntax.bind._
import scalaz.stream._
import io._
import scalaz.stream.text._
import Processes._
import process1.lift
import control.Functions._

/**
 * A Fold[T] can be used to pass over a Process[Task, T].
 *
 * It has:
 *
 *  - accumulation, with an initial state, of type S, a fold action and an action to perform with the last state
 *
 *  - side-effects with a Sink[Task, (T, S)] to write to a file for example, using the current element in the Process
 *    and the current accumulated state
 *
 * This covers many of the needs of iterating over a Scalaz stream and is composable because there is a Monoid
 * instance for Folds
 *
 */
trait Fold[T] {
  type S

  def prepare: Task[Unit]
  def sink: Sink[Task, (T, S)]
  def fold: (T, S) => S
  def init: S
  def last(s: S): Task[Unit]

  /** create a Process1 returning the state values */
  def foldState1: Process1[T, S] =
    Processes.foldState1(fold)(init)

  /** create a Process1 returning the folded elements and the state values */
  def zipWithState1: Process1[T, (T, S)] =
    Processes.zipWithState1(fold)(init)

}

/**
 * Fold functions and typeclasses
 */
object Fold {

  /**
   * Create a Fold from a Sink with no accumulation
   */
  def fromSink[T](aSink: Sink[Task, T]) =  new Fold[T] {
    type S = Unit
    lazy val sink: Sink[Task, (T, S)] = aSink.extend[S]

    def prepare = Task.now(())
    def fold = (t: T, u: Unit) => u
    def init = ()
    def last(u: Unit) = Task.now(u)
  }

  /**
   * Transform a simple sink where the written value doesn't depend on the
   * current state into a sink where the current state is passed all the time
   * (and actually ignored)
   * Create a Fold a State function
   */
  def fromState[T, S1](state: (T, S1) => S1)(initial: S1) = new Fold[T] {
    type S = S1
    lazy val sink: Sink[Task, (T, S)] = unitSink[T, S]

    def prepare = Task.now(())
    def fold = state
    def init = initial
    def last(s: S) = Task.now(())
  }

  /**
   * Create a Fold from a side-effecting function
   */
  def fromFunction[T](f: T => Task[Unit]): Fold[T] =
    fromSink(Process.constant(f))

  /**
   * Create a Fold from a Reducer
   */
  def fromReducer[T, S1](reducer: Reducer[T, S1]): Fold[T] = new Fold[T] {
    type S = S1
    lazy val sink: Sink[Task, (T, S)] = unitSink[T, S]

    def prepare = Task.now(())
    def fold = reducer.cons
    def init = reducer.monoid.zero
    def last(s: S) = Task.now(())
  }

  /**
   * Create a Fold from a Reducer and a last action
   */
  def fromReducerAndLast[T, S1](reducer: Reducer[T, S1], lastTask: S1 => Task[Unit]): Fold[T] = new Fold[T] {
    type S = S1
    lazy val sink: Sink[Task, (T, S)] = unitSink[T, S]

    def prepare = Task.now(())
    def fold = reducer.cons
    def init = reducer.monoid.zero
    def last(s: S) = lastTask(s)
  }

  /**
   * This Sink doesn't do anything
   * It can be used to build a Fold that does accumulation only
   */
  def unitSink[T, S]: Sink[Task, (T, S)] =
    channel((tu: (T, S)) => Task.now(()))

  /**
   * Unit Fold with no side-effect or accumulation
   */
  def unit[T] = fromSink(channel((t: T) => Task.now(())))

  /**
   * Unit fold function
   */
  def unitFoldFunction[T]: (T, Unit) => Unit = (t: T, u: Unit) => u

  /** create a fold sink to output lines to a file */
  def showToFilePath[T : Show, S](path: FilePath): Sink[Task, (T, S)] =
    io.fileChunkW(path.path).pipeIn(lift(Show[T].shows) |> utf8Encode).extend[S]

  implicit class FoldOps[T](val fold: Fold[T]) {
  }

  /**
   * Monoid for Folds, where effects are sequenced
   */
  implicit def foldMonoid[T]: Monoid[Fold[T]] = new Monoid[Fold[T]] {
    def append(f1: Fold[T], f2: =>Fold[T]): Fold[T] = f1 >> f2
    lazy val zero = Fold.unit[T]
  }

  /**
   * create a new Fold sequencing the effects of 2 Folds
   */
  implicit class sequenceFolds[T](val fold1: Fold[T]) {
    def >>(fold2: Fold[T]) = new Fold[T] {
      type S = (fold1.S, fold2.S)

      def prepare = fold1.prepare >> fold2.prepare

      def sink = fold1.sink.zipWith(fold2.sink) { (f1: ((T, fold1.S)) => Task[Unit], f2: ((T, fold2.S)) => Task[Unit]) =>
        (ts: (T, S)) => {
          val (t, (s1, s2)) = ts
          (f1((t, s1)) |@| f2((t, s2)))((_,_))
        }
      }

      def fold = (t : T, s12: (fold1.S, fold2.S)) => (fold1.fold(t, s12._1), fold2.fold(t, s12._2))
      def last(s12: (fold1.S, fold2.S)) = (fold1.last(s12._1) |@| fold2.last(s12._2))((_,_))
      def init = (fold1.init, fold2.init)
    }
  }

  /**
   * Run a fold an return the last value
   */
  def runFoldLast[T](process: Process[Task, T], fold: Fold[T]): Task[fold.S] =
    fold.prepare >>
    logged(process |> fold.zipWithState1).drainW(fold.sink).map(_._2).runLastOr(fold.init)

  /**
   * Run a Fold an let it perform a last action with the accumulated state
   */
  def runFold[T](process: Process[Task, T], fold: Fold[T]): Task[Unit] =
    runFoldLast(process, fold).flatMap(fold.last)

  /**
   * Run a list of Folds, sequenced with the Fold Monoid
   */
  def runFolds[T](process: Process[Task, T], folds: List[Fold[T]]): Task[Unit] =
    runFold(process, folds.suml)

}


답변