State, Streams

и все, все, все...

When you are a Bear of Very Little Brain, and you Think of Things, you find sometimes that a Thing which seemed very Thingish inside you is quite different when it gets out into the open and has other people looking at it.

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

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

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

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

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

Задача

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

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

Императивно

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(lfsr: StatefulLFSR): (Int, Int, Int) = 
  (lfsr.next(), lfsr.next(), lfsr.next())
// scala> three(new StatefulLFSR(0xACE1))
// res0: (Int, Int, Int) = (22128,43832,21916)
def period(lfsr: StatefulLFSR): Int = {
  val seed = lfsr.seed
  var period = 0
  while (seed != lfsr.next()) period += 1
  period
}
// scala> period(new StatefulLFSR(0xACE1))
// res1: Int = 65534

Многопоточно...

def parallel(lfsr: StatefulLFSR)(implicit ec: ExecutionContext): Future[Int] = {
  def eSet = Future {
    val rs = mutable.Set.empty[Int]
    (0 until 10000).foreach(_ => rs += lfsr.next())
    rs
  }
  Future.sequence(List.fill(2)(eSet)).map(_.reduce(_ & _).size)
}
//scala> Await.result(parallel(new StatefulLFSR(0xACE1)), 10.seconds)
//res3: Int = 4325
def parallel(lfsr: StatefulLFSR)(implicit ec: ExecutionContext): Future[Int] = {
  def eSet = Future {
    val rs = mutable.Set.empty[Int]
    (0 until 10000).foreach(_ => rs += lfsr.synchronized(lfsr.next()))
    rs
  }
  Future.sequence(List.fill(2)(eSet)).map(_.reduce(_ & _).size)
}
//scala> Await.result(parallel(new StatefulLFSR(0xACE1)), 10.seconds)
//res4: Int = 0

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

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(lfsr: StatelessLFSR): (Int, Int, Int) = 
  (next(lfsr).seed, next(lfsr).seed, next(lfsr).seed)
//scala> three(new StatelessLFSR(0xACE1))
//res0: (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(lfsr0: StatelessLFSR): (Int, Int, Int) = {
  val (lfsr1, i1) = next(lfsr0)
  val (lfsr2, i2) = next(lfsr1)
  val (_, i3)     = next(lfsr2)
  (i1, i2, i3)
}
//scala> three(new StatelessLFSR(0xACE1))
//res1: (Int, Int, Int) = (22128,43832,21916)
def period(lfsr0: StatelessLFSR): Int = {
  val seed = lfsr0.seed
  var (lfsr1, nxt) = next(lfsr0)
  var period      = 0

  while (seed != nxt) {
    // (lfsr1, nxt) = next(lfsr1) would be so cool
    val (l, n) = next(lfsr1) 
    period += 1
    lfsr1 = l
    nxt = n
  }
  period
}
//scala> period(new StatelessLFSR(0xACE1))
//res2: Int = 65534
def parallel(lfsr0: StatelessLFSR)(implicit ec: ExecutionContext): Future[Int] = {
  def eSet = Future {
    (0 until 10000)
      .foldLeft(lfsr0 -> immutable.Set.empty[Int]) {
        case ((lfsr1, rs), _) =>
          val (lfsr2, nxt) = next(lfsr1)
          (lfsr2, rs + nxt)
      }
      ._2
  }
  Future.sequence(List.fill(2)(eSet)).map(_.reduce(_ & _).size)
}
//scala> Await.result(parallel(new StatelessLFSR(0xACE1)), 10.seconds)
//res0: Int = 10000
//
//scala> Await.result(parallel(new StatelessLFSR(0xACE1)), 10.seconds)
//res1: Int = 10000
//WTF?!

Многопоточно...

def parallel(lfsr0: StatelessLFSR)(implicit ec: ExecutionContext): Future[Int] = {
  val ar = new AtomicReference(lfsr0)
  def eSet = Future {
    (0 until 10000)
      .foldLeft(immutable.Set.empty[Int]) {
        case (rs, _) =>
          var lfsr1        = ar.get()
          var (lfsr2, nxt) = next(lfsr1)
          var rs1          = rs + nxt

          while (!ar.compareAndSet(lfsr1, lfsr2)) {
            rs1 -= nxt
            lfsr1 = ar.get()
            val (l, n) = next(lfsr1)
            lfsr2 = l
            nxt = n
            rs1 = rs1 + nxt
          }
          rs1
      }
  }
  Future.sequence(List.fill(2)(eSet)).map(_.reduce(_ & _).size)
}
//scala> Await.result(parallel1(new StatelessLFSR(0xACE1)), 10.seconds)
//res3: Int = 0

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

def next(lfsr: StatelessLFSR): (StatelesLFSR, Int) = ...
val next: (StatelessLFSR) => (StatelessLFSR, Int)  = ...

def run[S, A]: (S) => (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
}
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)

  implicit val StateMonad: Monad[State[S, ?]] = 
    new Monad[State[S, ?]] { .. }
}

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

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 next: LFSR[Int] = State { lfsr =>
    import lfsr._
    val nxt = lfsr.copy(seed = (seed >> 1) | ((bitCount(seed & mask) & 1) << 15))
    (nxt, nxt.seed)
  }
}
val three: LFSR[(Int, Int, Int)] = for {
  i1 <- next
  i2 <- next
  i3 <- next
} yield (i1, i2, i3)
//three: State[lfsr.StatelessLFSR,(Int, Int, Int)] = cats.data.IndexedStateT@b2b8a14

//scala> three.runA(StatelessLFSR(0xACE1)).value
//res0: (Int, Int, Int) = (22128,43832,21916)
def period(lfsr0: StatelessLFSR): Int = {
  val seed = lfsr0.seed
  var (lfsr1, nxt) = next.run(lfsr0).value
  var period      = 0

  while (seed != nxt) {
    val (l, n) = next.run(lfsr1).value
    period += 1
    lfsr1 = l
    nxt = n
  }
  period
}
//scala> period(StatelessLFSR(0xACE1))
//res0: Int = 65534

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

def period(lfsr: StatelessLFSR): Int = {
  val seed = lfsr.seed
  val go: 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)))
  }

  go.runA(StatelessLFSR(seed)).value
}
//scala> period(StatelessLFSR(0xACE1))
//res1: Int = 65534
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

class Stream[F[_], +O] { .. }
type  Pipe[F[_], -I, +O] = Stream[F, I] => Stream[F, O]
type  Sink[F[_], -I]     = Pipe[F, I, Unit]
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)] = ...

  // ...
}
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 three(lfsr: StatelessLFSR): List[Int] = Stream
  .constant(next)          // Stream(next, next, ..)
  .through(runState(lfsr)) // (StatelessLFSR, LFSR[A]) => (StatelessLFSR, A)
  .take(3)                 // first 3 elements
  .toList                  // Stream => List
//scala> three(StatelessLFSR(0xACE1))
//res0: List[Int] = List(22128, 43832, 21916)
def period(lfsr: StatelessLFSR): Int = Stream
  .constant(next)            // Stream(next, next, ..)
  .through(runState(lfsr))   // (StatelessLFSR, LFSR[A]) => (StatelessLFSR, A)
  .takeWhile(_ != lfsr.seed) // while current not equal first
  .foldMap(_ => 1)           // add one for each element
  .lastOr(0)                 // result or zero
  .toList                    // Stream => List
  .head                      // result
//scala> period(StatelessLFSR(0xACE1))
//res1: Int = 65534

Асинхронно!

sealed abstract class IO[+A] {
  final def unsafeToFuture(): Future[A] = ..
}
object IO {
  def apply[A](a: => A): IO[A] = ..
  def shift(implicit ec: ExecutionContext): IO[Unit] = ..
}
class Stream[F[_], +O] {
  def compile: Stream.ToEffect[F, O] = ..

  def evalMap[F2[_], O2](f: O => F2[O2]): Stream[F2, O2] = ..
  
  def merge[O2 >: O](that: Stream[F, O2])(implicit F: Effect[F],
                                          ec: ExecutionContext): Stream[F, O2] = ..
}
object Stream {
  class ToEffect[F[_], O] {
    def last(implicit F: Sync[F]): F[Option[O]] = ..
    def toList(implicit F: Sync[F]): F[List[O]] = ..
    
    def fold[B](init: B)(f: (B, O) => B)(implicit F: Sync[F]): F[B] = ..
  }
}
def parallel(lfsr: StatelessLFSR)(implicit ec: ExecutionContext): Future[Option[Int]] = {
  val nxt = Stream
    .constant(next)                                          // Stream(next, next, ..)
    .zipWithIndex                                            // A => (A, Int)
    .evalMap {                                  
      case (l, i) if i % 128 == 0 => IO.shift *> IO(l)       // async barrier
      case (l, _) => IO(l)                                   // evaluate A
    }

  val str1 = nxt.map(_.map(v => Set(v).leftIor[Set[Int]])).take(10000)
  val str2 = nxt
    .map(_.map { v =>                                  
      Set(v).rightIor[Set[Int]]                              // A => Ior(Set[A], Set[A])
    })
    .take(10000)                                             // first 10'000 elements

  str1
    .merge(str2)                                             // from one Stream or another
    .through(runState(lfsr))
    .fold(Ior.both(Set.empty[Int], Set.empty[Int]))(_ |+| _) // combine
    .map(_.fold(identity, identity, _ & _))                  // intersect results
    .map(_.size)                                             // result cardinality
    .compile.last                                            // Stream[IO, A] => IO[Option[A]]
    .unsafeToFuture()                                        // IO[A] => Future[A]
}
//scala> Await.result(parallel(StatelessLFSR(0xACE1)), 10.seconds)
//res0: Option[Int] = Some(0)

Вопросы?!?

Нет, правда!

State, Streams et al

By Alexey Shuksto

State, Streams et al

Stateful Streams of Stateless State functions

  • 275