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
Благодарю за внимание
Эти слайды:
Cats vs ZIO
By Oleg Nizhnik
Cats vs ZIO
- 1,767