Stateful Streams of Stateless State functions

ЧТО?!?

В чем, собственно, вопрос?

Императивный подход

  • Общее состояние данные
  • Данные изменяемы
  • Побочные (и скрытые) действия

Функциональный подход

  • Ссылочная прозрачность
  • Данные неизменны
  • Чистые функции

Задача

Регистр сдвига с ЛОС

  • Данные: Регистр
  • Данные: Маска бита обратной связи
  • Функция: Следующее значение регистра
  • Характеристика: Период последовательности

Императивно

final class StatefulLFSR(var seed: Int, val mask: Int = 0x002D) {
  import java.lang.Integer.bitCount

  def next(): Int = {
    seed = (seed >> 1) | ((bitCount(seed & mask) & 1) << 15)
    seed
  }
}
def three(seed: Int): Unit = {
  val lfsr = new StatefulLFSR(seed)
  val i1 = lfsr.next()
  val i2 = lfsr.next()
  val i3 = lfsr.next()

  println(s"i1 = $i1, i2 = $i2, i3 = $i3")
}
scala> StatefulLFSR.three(0xACE1)
i1 = 22128, i2 = 43832, i3 = 21916
def period(seed: Int): Unit = {
  val lfsr = new StatefulLFSR(seed)
  var period = 0

  while (seed != lfsr.next()) period += 1

  println(s"period = $period")
}
scala> StatefulLFSR.period(0xACE1)
period = 65534

Функционально?

final case class StatelessLFSR(seed: Int, mask: Int = 0x002D)
object StatelessLFSR {
  import java.lang.Integer.bitCount

  def next(lfsr: StatelessLFSR): StatelessLFSR = {
    import lfsr._

    lfsr.copy(seed = (seed >> 1) | ((bitCount(seed & mask) & 1) << 15))
  }
}
def three(seed: Int): (Int, Int, Int) = {
  val lfsr = StatelessLFSR(seed)
  (next(lfsr).seed, next(lfsr).seed, next(lfsr).seed)
}
scala> StatelessLFSR.three(0xACE1)
res2: (Int, Int, Int) = (22128,22128,22128) // WTF?!

Функционально...

final case class StatelessLFSR(seed: Int, mask: Int = 0x002D)
object StatelessLFSR {
  import java.lang.Integer.bitCount

  def next(lfsr: StatelessLFSR): (StatelessLFSR, Int) = {
    import lfsr._

    val nxt = lfsr.copy(seed = (seed >> 1) | ((bitCount(seed & mask) & 1) << 15))
    (nxt, nxt.seed)
  }
}
def three(seed: Int): (Int, Int, Int) = {
  val lfsr0       = StatelessLFSR(seed)
  val (lfsr1, i1) = next(lfsr0)
  val (lfsr2, i2) = next(lfsr1)
  val (_, i3)     = next(lfsr2)

  (i1, i2, i3)
}
scala> StatelessLFSR.three(0xACE1)
res3: (Int, Int, Int) = (22128,43832,21916)
def period(seed: Int): Int = {
  var (lfsr, nxt) = next(StatelessLFSR(seed))
  var period      = 0

  while (seed != nxt) {
    // (lfsr, nxt) = next(lfsr) would be so cool
    val (l, n) = next(lfsr) 
    period += 1
    lfsr = l
    nxt = n
  }

  period
}
scala> StatelessLFSR.period(0xACE1)
res4: Int = 65534

Функция состояния

def next(lfsr: StatelessLFSR): (StatelesLFSR, Int) = ...
val next: (StatelessLFSR) => (StatelessLFSR, Int) = ...
def run[S, A]: (S) => (S, A) = ...
class State[S, A](run: (S) => (S, A)) { ??? }

State[S, A]

final class State[S, A](val run: (S) => (S, A)) { self =>
  val runA: (S) => A = run(_)._2
  val runS: (S) => S = run(_)._1

  def map[B](f: (A) => B): State[S, B] = State[S, B] { s0 =>
    val (s1, a) = self.run(s0)
    (s1, f(a))
  }
  def flatMap[B](f: (A) => State[S, B]): State[S, B] = State[S, B] { s0 =>
    val (s1, a) = self.run(s0)
    val (s2, b) = f(a).run(s1)
    (s2, b)
  }
}
object State {
  def apply[S, A](f: (S) => (S, A)): State[S, A] = new State[S, A](f)

  def inspect[S, A](f: (S) => A): State[S, A] = State(s => (s, f(s)))
  def modify[S](f: (S) => S): State[S, Unit]  = State(s => (f(s), ()))

  def get[S]: State[S, S]           = inspect(identity)
  def set[S](s: S): State[S, Unit]  = modify(_ => s)
  def pure[S, A](a: A): State[S, A] = inspect(_ => a)
}

Функционально!

final case class StatelessLFSR(seed: Int, mask: Int = 0x002D)
object StatelessLFSR {
  import cats.data.State
  import java.lang.Integer.bitCount

  type LFSR[A] = State[StatelessLFSR, A]

  val shift: LFSR[Unit] = State.modify { lfsr =>
    import lfsr._
    lfsr.copy(seed = (seed >> 1) | ((bitCount(seed & mask) & 1) << 15))
  }

  val next: LFSR[Int] = {
    for {
      _ <- shift
      n <- State.inspect(_.seed)
    } yield n
  }
}
def three(seed: Int) = { // return type?!
  for {
    i1 <- next
    i2 <- next
    i3 <- next
  } yield (i1, i2, i3)
}
scala> StatelessLFSR.three(0xACE1)
res5: cats.data.IndexedStateT[
  cats.Eval,lfsr.StatelessLFSR,lfsr.StatelessLFSR,(Int, Int, Int)
] = cats.data.IndexedStateT@7585fa7 // ?!?!?!?!
val three: LFSR[Int] = for {
  i1 <- next                       // val i1 = lfsr.next()
  i2 <- next                       // val i2 = lfsr.next()
  i3 <- next                       // val i3 = lfsr.next()
} yield (i1, i2, i3)

def runThree(seed: Int): (Int, Int, Int) =
  three.runA(StatelessLFSR(seed)).value
// val lfsr = new StatefulLFSR(seed)
scala> StatelessLFSR.three(0xACE1)
res6: (Int, Int, Int) = (22128,43832,21916)
def period(seed: Int): Int = {
  var (lfsr, nxt) = next.run(StatelessLFSR(seed)).value
  var period      = 0

  while (seed != nxt) {
    val (l, n) = next.run(lfsr).value
    period += 1
    lfsr = l
    nxt = n
  }

  period
}
scala> StatelessLFSR.period(0xACE1)
res7: Int = 65534

State[S, ?] -- это Monad[F[?]]

def period(seed: Int): Int = {
  import cats.Monad
  import cats.implicits._

  val period: LFSR[Int] = Monad[LFSR].tailRecM(none[Int] -> 0) {
    case (Some(`seed`), p) => State.pure(Right(p))
    case (s, p)            => next.map { n => 
      Left(n.some -> s.fold(p)(_ => p + 1))
    }
  }

  period.runA(StatelessLFSR(seed)).value
}
trait Monad[F[_]] {
  // ...
  // calls f(a) till Right(b)
  def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] = ...
}

Потоки, aka Streams

package fs2

abstract class Stream[F[_], +O] {
  def map[O2](f: O => O2): Stream[F, O2] = ...
  def flatMap[O2](f: O => Stream[F, O2]): Stream[F, O2] = ...

  // ...

  def mapAccumulate[S, O2](init: S)(
    f: (S, O) => (S, O2)
  ): Stream[F, (S, O2)] = ...
}

type Pipe[F[_], -I, +O] = Stream[F, I] => Stream[F, O]
type Sink[F[_], -I]     = Pipe[F, I, Unit]
def three(seed: Int): List[Int] = Stream
  .constant(next)
  .through(runState(StatelessLFSR(seed)))
  .take(3)
  .toList
scala> StatelessLFSR.three(0xACE1)
res8: List[Int] = List(22128, 43832, 21916)
def runState[F[_], S, A](init: S): Pipe[F, State[S, A], A] = { in =>
  in.mapAccumulate(init)((init, sa) => sa.run(init).value)
    .map(_._2)
}
def period(seed: Int): Int = Stream
  .constant(next)
  .through(runState(StatelessLFSR(seed)))
  .takeWhile(_ != seed)
  .foldMap(_ => 1)
  .lastOr(0)
  .toList
  .head
scala> StatelessLFSR.period(0xACE1)
res9: Int = 65534
def runState[F[_], S, A](init: S): Pipe[F, State[S, A], A] = { in =>
  in.mapAccumulate(init)((init, sa) => sa.run(init).value)
    .map(_._2)
}

Асинхронно!?

package cats

type State[S, A] = StateT[Eval, S, A] // StateT[F[_], S, A]

trait Eval[A] { def value: A }
object Eval {
  def now[A](a: A): Eval[A]            = ...
  def later[A](a: => A): Eval[A]       = ...
  def defer[A](a: => Eval[A]): Eval[A] = ...
}
package cats.effect

class IO[A] { .. } // ain't no simple `value: A` :(
object IO {
  def pure[A](a: A): IO[A]           = ... // Eval.now
  def apply[A](a: => A): IO[A]       = ... // Eval.later
  def suspend[A](a: => IO[A]): IO[A] = ... // Eval.defer

  def async[A](k: (Either[Throwable, A] => Unit) => Unit): IO[A] = ... 

  def eval[A](fa: Eval[A]): IO[A] = ...
}
scala> next.runA(StatelessLFSR(0xACE1)).value // .value?!
res10: Int = 22128
scala> val state = next
state: lfsr.StatelessLFSR.LFSR[Int] = cats.data.IndexedStateT@7d035352

scala> val eval = state.run(StatelessLFSR(0xACE1))
eval: cats.Eval[(lfsr.StatelessLFSR, Int)] = cats.Eval$$anon$6@7956cb16

scala> eval.value
res10: (lfsr.StatelessLFSR, Int) = (StatelessLFSR(22128,45),22128)
def runState[F[_]: FlatMap, S, A](init: S): Pipe[F, StateT[F, S, A], A] = {
  def go(str: Stream[F, StateT[F, S, A]], init: S): Pull[F, A, Unit] =
    str.pull.uncons1.flatMap {
      case None => Pull.done
      case Some((sa, rst)) =>
        Pull.eval(sa.run(init)).flatMap {
          case (s, a) => Pull.output1(a).flatMap(_ => go(rst, s))
        }
    }
  
  in => go(in, init).stream
}
type Pipe[F[_], A, B] = Stream[F[_], A] => Stream[F[_], B]

type State[S, A] = ...
def runState[F[_], S, A](init: S): Pipe[F, State[S, A], A] = ...

type StateT[F[_], S, A] = ...
def runState[F[_], S, A](init: S): Pipe[F, StateT[F, S, A], A] = ???

Как-бы счет

final case class Account(amount: Int)
object Account {

  type State[A] = StateT[IO, Account, A]

  implicit val BalanceEq: Eq[Account]     = Eq.fromUniversalEquals
  implicit val BalanceShow: Show[Account] = Show.fromToString

}
// object Account {

  private def _deposit(amount: Int): State[Unit] = StateT.modifyF { b =>
    if (amount >= 0) IO(b.copy(amount = b.amount + amount))
    else IO.raiseError {
      new RuntimeException(s"Could not deposit negative amount: $amount.")
    }
  }

  def deposit(amount: Int): State[Account] =
    for {
      _ <- _deposit(amount)
      b <- StateT.get[IO, Account]
    } yield b

// }
// object Account {

  private def _interest(rate: Int): State[Unit] = StateT.modifyF { b =>
    if (rate > 0) IO(b.copy(amount = (b.amount * rate) / 100))
    else IO.raiseError {
      new RuntimeException(
        s"Could not accrual interest of non positive rate: $rate."
      )
    }
  }

  def interest(rate: Int): State[Account] = _interest(rate) *> StateT.get
  // for {
  //   _ <- _interest(rate)
  //   b <- StateT.get[IO, Account]
  // } yield b

// }
// object Account {

  private def _withdraw(amount: Int): State[Unit] = StateT.modifyF { b =>
    if (amount < 0) IO.raiseError { 
      new RuntimeException(s"Could not withdraw negative amount: $amount.")
    } else if (amount > b.amount) IO.raiseError {
      new RuntimeException(s"Insufficient funds: ${amount - b.amount}.")
    } else IO {
      b.copy(amount = b.amount - amount))
    }
  }

  def withdraw(amount: Int): State[Int] = _withdraw(amount).as(amount)
  // for {
  //   _ <- withdraw(amount)
  //   a <- State.pure[IO, Account, Int](amount)
  // } yield a

// }
object Finances extends StreamApp[IO] {
  import Account._
  import scala.concurrent.ExecutionContext.Implicits.global

  def stream(args: List[String], requestShutdown: IO[Unit])
  : Stream[IO, StreamApp.ExitCode] = Scheduler[IO](3)
    .flatMap { sch =>
      val deposits = sch.fixedRate[IO](30.millis).as {
        deposit(100).map(_.asLeft[Int])
      }
      val percents = sch.fixedRate[IO](30.millis).as {
        interest(101).map(_.asLeft[Int])
      }
      val withdraws = sch.fixedRate[IO](7.millis).as {
        withdraw(20).map(_.asRight[Account])
      }
      
      deposits.merge(percents).merge(withdraws)
    }
    .through(runState(Account(1000)))
    .mergeHaltR { 
      Stream
        .duration[IO]
        .takeWhile(_ <= 365.millis)
        .drain
    }
    .to(Sink.showLinesStdOut)
    .last
    .as(StreamApp.ExitCode.Success)
}
scala> Finances.main(Array())
Right(20)
Right(20)
Right(20)
Right(20)
Left(Account(929))
Right(20)
Left(Account(1009))
Right(20)
Right(20)
Right(20)
Left(Account(958))
Left(Account(1058))
Right(20)
Right(20)
Right(20)
Right(20)
Left(Account(1078))
Right(20)
Left(Account(1068))
Right(20)
Right(20)
Right(20)
Right(20)
Left(Account(1088))
Left(Account(1098))
Right(20)
Right(20)
Right(20)
Right(20)
Left(Account(1118))
Left(Account(1129))
Right(20)
Right(20)
Right(20)
Right(20)
Left(Account(1149))
Left(Account(1160))
Right(20)
Right(20)
Right(20)
Right(20)
Right(20)
Left(Account(1070))
Left(Account(1170))
Right(20)
Right(20)
Right(20)
Right(20)
Left(Account(1190))
Left(Account(1201))
Right(20)
Right(20)
Right(20)
Right(20)
Left(Account(1221))
Left(Account(1233))
Right(20)
Right(20)
Right(20)
Right(20)
Left(Account(1253))
Right(20)
Left(Account(1245))
Right(20)
Right(20)
Right(20)
Right(20)
Left(Account(1265))
Left(Account(1277))
Right(20)
Right(20)

Вопросы?!?

Нет, правда!

Made with Slides.com