val latestMessage: Stream[IO, Option[Message]] = ??? // Poll once an hour; if poll fails, retry with an exponential delay // Stream should emit last successfully polled message whenever pulled
Effect Async 10 • fs2.util defined major FP type classes • Bidirectional shims to Cats and Scalaz • Shims in separate libraries • Minimal • no Eq, Semigroup, Monoid • Error prone and confusing
Sync Async 11 • Core type classes from cats-core • Effect type classes from cats-effect • No type classes from fs2 • Use of Eq, Semigroup, Monoid, Show, ~>, Eval • fs2.async built on Effect and ExecutionContext • Lock-free concurrent data structures with referential transparency • MVar-like Ref[F,A] • Strategy is gone Effect
Cartesian CoflatMap Comonad ContravariantCartesian FlatMap Foldable Functor Inject InvariantMonoidal Monad MonadError MonoidK NotNull Reducible SemigroupK Show Bifunctor Contravariant Invariant Profunctor Strong Traverse Arrow Category Choice Compose a |@| b a *> b a <* b a <+> b a >>> b a <<< b Sync Async Effect LiftIO NonEmptyTraverse InjectK CommutativeArrow CommutativeFlatMap CommutativeMonad Cats Infographic by @tpolecat, https://github.com/tpolecat/cats-infographic/blob/master/cats.pdf, CC-BY-SA 4.0
abstract class Chunk[+O] extends Segment[O,Unit] { def size: Int def apply(i: Int): O } Potentially infinite, pure sequence of values of type O and a result of type R
a Stream happens as successive transformations of pure data Open File Read map map filter flatMap Read filter Close • Need operator fusion to avoid intermediate chunks • Segment fuses all operations via staging • Constructors & operations are implemented via anonymous subtypes of Segment which implement the stage0 method • Evaluators (e.g., run, toList, splitAt, unconsChunk) first stage the segment and then step through the staged machine until done
{ def flatMap[R2](f: R FreeC[F, R2]): FreeC[F, R2] def onError[R2>:R](h: Throwable FreeC[F,R2]): FreeC[F,R2] } case class Pure[F[_], R](r: R) extends FreeC[F, R] case class Eval[F[_], R](fr: F[R]) extends FreeC[F, R] case class Bind[F[_], X, R](fx: FreeC[F, X], f: Either[Throwable,X] FreeC[F, R]) extends FreeC[F, R] case class Fail[F[_], R](error: Throwable) extends FreeC[F,R] • Free monad with built-in exception handling • Supports explicit failures (via Fail) and exceptions thrown from pure functions (e.g., from flatMap’s f or onError’s h)
extends Algebra[F,O,Unit] case class Run[F[_],O,R](s: Segment[O,R]) extends Algebra[F,O,R] case class Eval[F[_],O,R](fr: F[R]) extends Algebra[F,O,R] case class Acquire[F[_],O,R](resource: F[R], release: R F[Unit]) extends Algebra[F,O,(R,Token)] case class Release[F[_],O](token: Token) extends Algebra[F,O,Unit] case class OpenScope[F[_],O]() extends Algebra[F,O,Scope[F]] case class CloseScope[F[_],O](s: Scope[F]) extends Algebra[F,O,Unit] case class UnconsAsync[F[_],X,Y,O](s: FreeC[Algebra[F,O,?],Unit], ec: ExecutionContext) extends Algebra[F,X,AsyncPull[…]]
loop(s: Stream[F,O], n: Long): Pull[F,O,Unit] = { if (n <= 0) Pull.done else s.pull.uncons1.flatMap { case Some((hd,tl)) Pull.output1(hd) loop(tl, n - 1) case None Pull.done } } in loop(in,n).stream } Take 1: Recurse on each element of the Stream
loop(s: Stream[F,O], n: Long): Pull[F,O,Unit] = { if (n <= 0) Pull.done else s.pull.unconsChunk.flatMap { case Some((hd,tl)) if (hd.size < n) Pull.output(hd) loop(tl, n - hd.size) else Pull.output(hd.strict.take(n)) case None Pull.done } } in loop(in,n).stream } Take 2: Recurse on each Chunk of the Stream
in.scanSegmentsOpt(n) { n if (n <= 0) None else Some(seg seg.take(n).mapResult { case Left((_,n)) n case Right(_) 0 }) } Take 3: Recurse on each Segment of the Stream