Scala

I

O

Scala

I

O

Cats Effect

ZIO

Cats Effect vs ZIO

slido.com with #077428

Haskell

Haskell

countZeros :: [Int] -> Int
countZeros numbers = 
      var count = 0
      do 
      	num <- numbers
        if num == 0 then
          count = count + 1
        return count

Haskell

countZeros :: [Int] -> Int
countZeros numbers = 
    let go accum (0 : rest) = go (accum + 1) rest
    	go accum (_ : rest) = go accum rest
        go accum []         = accum
    in go 0 numbers
 countZeros :: [Int] -> Int   
 countZeros = length . filter ( == 0)

Haskell

main :: ?
main = ?
main :: [String] -> [String]
main (firstLine : nextLine : ...) = let 

	in firstOutLine : nextOutLine : ...
def main(input: LazyList[String]): LazyList[String] = ???

enum LazyList[+A]:
   case Empty
   case Cons(head: () => A, tail: () => LazyList[A])
type Dialogue = [Request] -> [Response]
main :: Dialogue

Haskell

Monads

await

async

Monads

(>>=) :: IO a -> (a -> IO b) -> IO b

Monads

IO

type IO a = World -> IORes a
data IORes a = MkIORes a World
countZeros :: [Int] -> IO Int
countZeros nums = do
  count <- newIORef 0
  forM_ nums \num ->
    when (num == 0) $
      modifyIORef count (+ 1)
  readIORef count

Concurrency

forkIO :: IO () -> IO ThreadId
throwTo :: Exception e => ThreadId -> e -> IO ()

atomicModifyIORef :: IORef a -> (a -> (a, b)) -> IO b
atomicWriteIORef :: IORef a -> a -> IO ()

takeMVar :: MVar a -> IO a
putMVar :: MVar a -> a -> IO ()
readMVar :: MVar a -> IO a

writeChan :: Chan a -> a -> IO ()
readChan :: Chan a -> IO a

STM

newTVar :: a -> STM (TVar a)
readTVar :: TVar a -> STM a
writeTVar :: TVar a -> a -> STM ()

atomically :: STM a -> IO a
retry :: STM a

Async

async :: IO a -> IO (Async a)
wait :: Async a -> IO a

Resource

bracket
        :: IO a         -- ^ computation to run first (\"acquire resource\")
        -> (a -> IO b)  -- ^ computation to run last (\"release resource\")
        -> (a -> IO c)  -- ^ computation to run in-between
        -> IO c         -- returns the value from the in-between computation
allocate :: IO a -> IO () -> ResIO (ReleaseKey, a)
runResourceT :: ResIO a -> IO a

Streams

  • Conduit
  • Streaming
  • Streamly
  • ...

Scala

Akka

package akka.actor

trait Actor {
    abstract def receive: PartialFunction[Any, Unit]

2010

Future

package scala.concurrent

trait Future[+T]{
    def onComplete[U](f: Try[T] => U)(implicit ec: ExecutionContext): Unit
    
    def map[S](f: T => S)(implicit ec: ExecutionContext): Future[S] = ... 
    
    def flatMap[S](f: T => Future[S])(implicit ec: ExecutionContext): Future[S] = 
def businessFeature(arg: Arg, input: Input): Future[Result] = for {
       info   <- getInfo(arg)
       _      <- doSomething(input)
       result <- getResult(info, input)
    } yield result

2011

Monifu

2013

trait Scheduler extends ExecutionContext with Executor {
  def execute(command: Runnable): Unit
  def scheduleOnce(initialDelay: Long, unit: TimeUnit, r: Runnable): Cancelable
  def scheduleWithFixedDelay(
  	initialDelay: Long, delay: Long, unit: TimeUnit, r: Runnable): Cancelable
}

trait Observer[-A] extends Any with Serializable {
  def onNext(elem: A): Future[Ack]

  def onError(ex: Throwable): Unit

  def onComplete(): Unit
}

abstract class Observable[+A]{
  def subscribe(observer: Observer[A])(implicit s: Scheduler): Cancelable
}

Monix

2016

abstract class Callback[-A] {
  def onSuccess(value: A): Unit
  def onError(ex: Throwable): Unit
}

sealed abstract class Task[+A]{
  def runAsync(cb: Callback[A])(implicit s: Scheduler): Cancelable
}

abstract class MVar[A] {
  def put(a: A): Task[Unit]
  def take: Task[A]
  def read: Task[A]
}

scalaz

package scalaz
package effect

sealed abstract class IO[A] {
  private[effect] 
  	def apply(rw: World[RealWorld]): Trampoline[(World[RealWorld], A)]

2010

scalaz

package scalaz
package effect

sealed abstract class IO[A] {
  private[effect] 
  	def apply(rw: Tower[IvoryTower]): Trampoline[(Tower[IvoryTower], A)]

2010

scalaz-stream

2013

trait Process[+F[_], +O] 

object Process {
  case class Await[F[_], A, +O] private[stream](
    req: F[A], recv: A => Process[F,O],
    fallback: Process[F,O],
    cleanup: Process[F,O]) extends Process[F, O]

  case class Emit[F[_], O] private[stream](
    head: Seq[O], 
    tail: Process[F,O]) extends Process[F,O]

  case object Halt extends Process[Nothing, Nothing]

scalaz-stream

2015

Functional Streams For Scala

scalaz

cats

scalaz-stream

2015

F S F S

scalaz

cats

scalaz-stream

2015

(F S) ²

scalaz

cats

scalaz-stream

2015

FS2

scalaz

cats

FS2

final class Task[+A](private[fs2] val get: Future[Attempt[A]])

trait Semaphore[F[_]] {    
  def decrementBy(n: Long): F[Unit]
  def incrementBy(n: Long): F[Unit]
}

trait Queue[F[_], A] {
  def enqueue1(a: A): F[Unit]
  def dequeue1: F[A]
}

trait Topic[F[_], A] {
  def publish1(a: A): F[Unit]
  def subscribe(maxQueued: Int): Stream[F, A]
}

trait Signal[F[_], A] { 
  def get: F[A] 
  def set(a: A): F[Unit]
}

cats-effect

2017

sealed abstract class IO[+A]

case class Pure[+A](a: A)                                   extends IO[A]
case class Delay[+A](thunk: () => A)                        extends IO[A]
case class RaiseError(e: Throwable)                         extends IO[Nothing]
case class Suspend[+A](thunk: () => IO[A])                  extends IO[A]
case class Bind[E, +A](source: IO[E], f: E => IO[A])        extends IO[A]
case class Async[+A](
   k: (IOConnection, Either[Throwable, A] => Unit) => Unit) extends IO[A]

cats-effect

2017

trait Semaphore[F[_]] {    
  def decrementBy(n: Long): F[Unit]
  def incrementBy(n: Long): F[Unit]
}

abstract class MVar[A] {
  def put(a: A): Task[Unit]
  def take: Task[A]
  def read: Task[A]
}

trait Deferred[F[_], A] // Topic {
  def get: F[A]
  def complete(a: A): F[Unit]
}

abstract class Ref[F[_], A] // Signal {
  def get: F[A]
  def set(a: A): F[Unit]
}

scalaz8-effect

2017

scalaz-ioeffect

2018

scalaz-zio

2018

ZIO

2019

IO

IO

trait IO[+A]:
  def run(world: World): (A, World)

  def flatMap[B](f: A => IO[B]): IO[B] = world1 =>
    val (a, world2) = run(world1)
    f(a).run(world2)
    
  def unsafeRun() = run(new World)._1
def countZeros(xs: LazyList[Int]): IO[Int] =
  def go(xs: LazyList[Int], ref: IORef[Int]): IO[Unit] = xs match
    case 0 #:: rest => ref.update(_ + 1).flatMap(_ => go(rest, ref))
    case _ #:: rest => go(rest, ref)
    case _          => IO(())
  for
    ref <- IO.newIORef(0)
    _   <- go(xs, ref)
    x   <- ref.get
  yield x

Scala IO

enum IO[+A]:
  def flatMap[B](f: A => IO[B]): IO[B] = FlatMap(this, f)

  case Pure(a: A)
  case FlatMap[A, +B](a: IO[A], f: A => IO[B]) extends IO[B]

  @tailrec final def unsafeRun(): A = this match
    case Pure(a)        => a
    case FlatMap(fx, f) =>
      fx match
        case Pure(x)        => f(x).unsafeRun()
        case FlatMap(fy, g) =>
          fy.flatMap(y => g(y).flatMap(f)).unsafeRun()

Scala IO

enum IO[+A]:
  def flatMap[B](f: A => IO[B]): IO[B]                    = 
    Continue(this, _.fold(Throw(_), f))
  def handleWith[A1 >: A](h: Throwable => IO[A1]): IO[A1] = 
    Continue(this, _.fold(h, Pure(_)))

  case Pure(a: A)
  case Throw(err: Throwable)
  case Continue[A, +B](a: IO[A], f: Try[A] => IO[B]) extends IO[B]

  @tailrec private def unsafeRun1(): Try[A] = this match
    case Pure(a)         => Success(a)
    case Throw(err)      => Failure(err)
    case Continue(fa, f) =>
      fa match
        case Pure(a)         => f(Success(a)).unsafeRun1()
        case Throw(err)      => f(Failure(err)).unsafeRun1()
        case Continue(fx, g) => Continue(fx, ta => Continue(g(ta), f)).unsafeRun1()
        
  final def unsafeRun(): Try[A]             =
    try unsafeRun1()
    catch { case NonFatal(err) => Failure(err) }

Scala IO

enum IO[+A]:
  case Pure(a: A)
  case Throw(err: Throwable)
  case Continue[A, +B](a: IO[A], f: Try[A] => IO[B]) extends IO[B]
  case Async(cont: (Try[A] => Unit) => Unit, ec: ExecutionContext)

  @tailrec private def unsafeRun1(callback: Try[A] => Unit): Unit = this match
    case Pure(a)         => callback(Success(a))
    case Throw(err)      => callback(Failure(err))
    case Async(cont, _)  => cont(callback)
    case Continue(fa, f) =>
      fa match
        case Pure(a)         => f(Success(a)).unsafeRun1(callback)
        case Throw(err)      => f(Failure(err)).unsafeRun1(callback)
        case Continue(fx, g) => Continue(fx, u => Continue(g(u), f)).unsafeRun1(callback)
        case Async(cont, ec) => cont(e => ec.execute(() => f(e).unsafeRun(callback)))

  final def unsafeRun(callback: Try[A] => Unit): Unit             =
    try unsafeRun1(callback)
    catch { case NonFatal(err) => callback(Failure(err)) }

Common

Common::Ref

abstract class Ref[F[_], A] {
  def get: F[A]
  def set(a: A): F[Unit]
  def modify[B](f: A => (A, B)): F[B]
  def tryModify[B](f: A => (A, B)): F[Option[B]]
 }
 
 sealed abstract class Ref[A]{
  def get: UIO[A]
  def set(a: A): UIO[Unit]
  def modify[B](f: A => (B, A)): UIO[B]
  def setAsync(a: A): UIO[Unit]
 }

Common::Promise

abstract class Deferred[F[_], A] {
  def get: F[A]
  def complete(a: A): F[Unit]
 }
 
final class Promise[E, A]{
  def await: IO[E, A]
  def completeWith(io: IO[E, A]): UIO[Boolean]
}

Common::Semaphore

abstract class Semaphore[F[_]] {
  def acquire: F[Unit]
  def release: F[Unit]
  def permit: Resource[F, Unit]
}
 
final class Semaphore{
  def withPermits[R, E, A](n: Long)(task: ZIO[R, E, A]): ZIO[R, E, A]
  def withPermitManaged[R, E]: ZManaged[R, E, Unit]
}

Common::Queue

abstract class Queue[F[_], A] {
  def offer(a: A): F[Unit]
  def take: F[A]
}
 
abstract class Queue[A]{
  def offer(a: A): UIO[Boolean]
  def take: UIO[A]
}

Common::Resource

sealed abstract class Resource[F[_], +A]  {
  def use[B](f: A => F[B])(implicit F: BracketThrow[F]): F[B]
  def flatMap[B](f: A => Resource[F, B]): Resource[F, B]
}

object Resource{
  def make[F[_]: Functor, A](
    acquire: F[A])(
    release: A => F[Unit]): Resource[F, A]
}
 
abstract class ZManaged[-R, +E, +A]{
  def use[B](f: A => ZIO[R, E, B]): ZIO[R, E, B]
  def flatMap[R, E, B](f: A => ZManaged[R, E, B]): ZManaged[R, E, B]
}

object ZManaged{
  def make[R, E, A](
    acquire: ZIO[R, E, A])(
    release: A => ZIO[R, Nothing, Any]): ZManaged[R, E, A]
}

Common::Resource

val application: Resource[IO, Unit] = for {
   config <- readConfig
   state  <- initializeState(config)
   db     <- initializeDB(config.db)
   kafka  <- connectToKafka(config.kafka)
   _      <- startKafkaListeners(config.topics, kafka, db)
   _      <- runBackroundWorkers(config.bg)
   _      <- startHttpServer(config.http, db)
   _      <- IO.never
} yield ()

Common::Fiber

sealed abstract class IO[+A]{
  def start: IO[Fiber[IO, Throwable, A]]
}

trait GenSpawn[F[_], E]{
  def start[A](fa: F[A]): F[Fiber[F, E, A]]
}

trait Fiber[F[_], E, A]{
  def join: F[Outcome[F, E, A]]
  def cancel: F[Unit]
}
 
sealed trait ZIO[-R, +E, +A]{
  final def fork: URIO[R, Fiber[E, A]]
}

sealed abstract class Fiber[+E, +A]{
  def await: UIO[Exit[E, A]]
  def interrupt: UIO[Exit[E, A]]
}

Common::FiberLocal

sealed abstract class IOLocal[+A]{
  def get: IO[A]
  def set(value: A): IO[Unit]
  def reset: IO[Unit]
}

object IOLocal {
  def apply[A](default: A): IO[IOLocal[A]] 
}

final class FiberRef[A]{
  val get: UIO[A]  
  def set(value: A): UIO[Unit]
}

object FiberRef {
  def make[A](initial: A, 
    fork: A => A = (a: A) => a, 
    join: (A, A) => A = ((_: A, a: A) => a)): UIO[FiberRef[A]] =
}

Common

  • IO монада

  • Структуры коммуникации

    • Ref
    • Queue
    • Promise
    • Semaphore
    • FiberLocals
  • Fiber concurrency

  • Структурный контроль за ресурсами

Compete

Compete::Performance

Compete::Streams

FS2

  • Pull-based
  • Doobie
  • Http4s
  • ....

zio-streams

  • iterator-like
  • ...:)??

Compete::Streams

FS2

  • Pull-based
  • Doobie
  • Http4s
  • ....

zio-streams

  • iterator-like
  • ...:)??

Compete::Context

case class ApplicationContext(
   traceId: TraceId,
   spanId: SpanId,
   currentUser: User,
   pgPool: PostgresPool[IO],
   config: Config
)
type F[A] = ReaderT[ApplicationContext, IO, A]

final case class Kleisli[F[_], -A, B](run: A => F[B]){
  def local[C](f: C => A): Kleisli[F, C, B]
  def ask[F[_], A](implicit F: Applicative[F]): Kleisli[F, A, A]
}
sealed trait ZIO[-R, +E, +A]{
  def provide(r: R): IO[E, A] = ZIO.provide(r)(self)
  def provideSome[C](f: C => R): ZIO[C, E, A]
}

object ZIO{
  def environment[R]: URIO[R, R] = access(r => r)
}

Cats-Effect 

ZIO

Compete::Context

case class ApplicationContext(
   traceId: TraceId,
   spanId: SpanId,
   currentUser: User,
   pgPool: PostgresPool[IO],
   config: Config
)
type F[A] = ReaderT[ApplicationContext, IO, A]

final case class Kleisli[F[_], -A, B](run: A => F[B]){
  def local[C](f: C => A): Kleisli[F, C, B]
  def ask[F[_], A](implicit F: Applicative[F]): Kleisli[F, A, A]
}
sealed trait ZIO[-R, +E, +A]{
  def provide(r: R): IO[E, A] = ZIO.provide(r)(self)
  def provideSome[C](f: C => R): ZIO[C, E, A]
}

object ZIO{
  def environment[R]: URIO[R, R] = access(r => r)
}

Cats-Effect 

ZIO

Compete::Unsafe Execution

trait Effect[F[_]]
     def runAsync[A](fa: F[A])(cb: Either[Throwable, A] => IO[Unit]): SyncIO[Unit]
}
trait Dispatcher[F[_]]{
     def unsafeToFuture[A](fa: F[A]): Future[A]
}
object Dispatcher{
     def apply[F[_]](implicit F: Async[F]): Resource[F, Dispatcher[F]]
}
object ZIO{
   def runtime[R]: URIO[R, Runtime[R]]
}
trait Runtime[R]{
  def unsafeRunAsync[E, A](zio: => ZIO[R, E, A])(k: Exit[E, A] => Any): Unit
}

Cats-Effect 2

Cats-Effect 3

ZIO

Compete::Unsafe Execution

trait Effect[F[_]]
     def runAsync[A](fa: F[A])(cb: Either[Throwable, A] => IO[Unit]): SyncIO[Unit]
}
trait Dispatcher[F[_]]{
     def unsafeToFuture[A](fa: F[A]): Future[A]
}
object Dispatcher{
     def apply[F[_]](implicit F: Async[F]): Resource[F, Dispatcher[F]]
}
object ZIO{
   def runtime[R]: URIO[R, Runtime[R]]
}
trait Runtime[R]{
  def unsafeRunAsync[E, A](zio: => ZIO[R, E, A])(k: Exit[E, A] => Any): Unit
}

Cats-Effect 2

Cats-Effect 3

ZIO

Compete::DomainErrors

trait UserRepository{
  def findUser(id: UserId): IO[Either[UserNotFound, User]]
  def createUser(user: User): IO[Either[AlreadyExists, UserId]]
}
trait UserRepository{
  def findUser(id: UserId): EitherT[IO, UserNotFound, User]]
  def createUser(user: User): EitherT[IO, AlreadyExists, UserId]]
}
trait UserRepository{
  def findUser(id: UserId): IO[UserNotFound, User]
  def createUser(user: User): IO[AlreadyExists, UserId]
}

Vanilla

Cats-Effect 

ZIO

Compete::DomainErrors

trait UserRepository{
  def findUser(id: UserId): IO[Either[UserNotFound, User]]
  def createUser(user: User): IO[Either[AlreadyExists, UserId]]
}
trait UserRepository{
  def findUser(id: UserId): EitherT[IO, UserNotFound, User]]
  def createUser(user: User): EitherT[IO, AlreadyExists, UserId]]
}
trait UserRepository{
  def findUser(id: UserId): IO[UserNotFound, User]
  def createUser(user: User): IO[AlreadyExists, UserId]
}

Vanilla

Cats-Effect 

ZIO

Cats Effect Specials

  • F[_] + Type-class hierarchy

  • Fiber-aware thread pool (zio 2?)

ZIO Specials

  • STM (cats-stm?)

  • Module-pattern

    • Module-pattern 2.0 + ZLayer

    • Distage

  • ZSchedule (fs-timeseries?)

Итоги

  • Используйте cats-effect

  • Используйте ZIO

  • Используйте Tofu

Благодарю за внимание

Эти слайды:

slides.com/olegnizhnik/scala_ios

Cats vs ZIO

By Oleg Nizhnik

Cats vs ZIO

  • 1,767