Cats и Cats Effects

Cats

Cats Book

Cats

Предоставя:

Data types

Синтаксис

Синтаксис – Option

import cats.syntax.option.*

val maybeOne = 1.some // Some(1): Option[Int]
val maybeN = none[Int] // None: Option[Int]

val either = maybeOne.toRightNec("It's not there :(") // Right(1): Either[String, Int]
val validated = maybeOne.toValidNec("It's not there :(") // Left("..."): Either[String, Int]

val integer = maybeN.orEmpty // 0

Синтаксис – Either и Validated

import cats.syntax.either.*

val eitherOne = 1.asRight
val eitherN = "Error".asLeft

val eitherOneChain = 1.rightNec
val eitherNChain = "Error".leftNec

val recoveredEither = eitherN.recover {
  case "Error" => 42.asRight
}

eitherOneChain.toValidated
import cats.syntax.validated.*

val validatedOne = 1.validNec
val validatedN = "Error".invalidNec

validatedOne.toEither

Type class-ове

Поглед над йеархиите

Type class-ове и синтаксис чрез implicit

Да разгледаме отново дефинирането на Type Class-ове в Scala 3 и Scala 2

  • В Scala 3 инстанциите на type class-овете идват заедно с техния синтаксис (extension методи)
  • В Scala 2 инстанциите и синтаксиса са разделени и е нужно да бъдат import-нати и двете
  • Scala 2 използва implicit класове за extension методи
  • В Scala 2 вместо given инстанции се използват implicit val-ове и def-ове
  • В Scala 2 вместо using параметри се декларират implicit параметри
  • Всичко останало работи по подобен начин
  • Scala 3 позволява използване на given, където се очаква implicit, и използване на implicit, където се очаква using

Type class-ове и синтаксис чрез implicit

Cats използва изцяло Scala 2 синтаксиса за Type Class-ове (чрез implicit)

Сравнение и наредба

trait Eq[A]:
  def eqv(x: A, y: A): Boolean

  def neqv(x: A, y: A): Boolean = !eqv(x, y)

Semigroup и Monoid

trait Semigroup[A]:
  def combine(x: A, y: A): A
trait Monoid[A] extends Semigroup[A]:
  def empty: A

Semigroup и Monoid синтаксис

import cats.syntax.monoid.*

1 |+| 2 // 3
"ab".combineN(3) // "ababab"

0.isEmpty // true

Semigroup[Int].combineAllOption(List(1, 2, 3)) // Some(6)
Monoid[Int].combineAll(List(1, 2, 3)) // 6

Тестване на аксиоми

Foldable

trait Foldable[F[_]]:
  def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B
  def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B]

  • Функтор
  • Апликатив
  • Монада

Functor

trait Functor[F[_]]:
  def map[A, B](fa: F[A])(f: A => B): F[B]

Apply

trait Apply[F[_]] extends Functor[F]:
  def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
  
  def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] =
    ap(map(fa)(a => (b: B) => (a, b)))(fb)

  def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] =
    map(product(fa, fb))(f.tupled)

Applicative

trait Applicative[F[_]] extends Apply[F]:
  def pure[A](x: A): F[A]

  def map[A, B](fa: F[A])(f: A => B): F[B] =
    ap(pure(f))(fa)

Traverse

trait Traverse[F[_]] extends Functor[F] with Foldable[F]:
  def traverse[G[_] : Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]

  def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] =
    traverse(fga)(ga => ga)

Apply, Applicative, Traverse синтаксис

FlatMap

trait FlatMap[F[_]] extends Apply[F]:
  def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]

Monad

trait Monad[F[_]] extends FlatMap[F] with Applicative[F]

MonadError

trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F]

Абстрактни членове:

def raiseError[A](e: E): F[A]
def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A]

FlatMap, Monad и MonadError синтаксис

Parallel

trait Parallel[M[_]] extends NonEmptyParallel[M]:
  type F[_]
  
  def applicative: Applicative[F]
  def monad: Monad[M]

  def sequential: F ~> M
  def parallel: M ~> F

Композиция на функтор, апликатив и монада

Композиция на монади – OptionT и EitherT

Free Монада:

Cats Effect

“Framework to build composable typesafe functional concurrency libraries and applications.” – Cats Effect

Cats Effect

  • Type class-ове за конкурентни ефекти
  • Иплементация на type class-овете, наречена IO
  • Използва Fiber като лека конкурентна абстракция
  • Предоставя среда за изпълнение на Fiber-и
  • Инструменти за менажиране на ресурси и споделяне на информация между fiber-и
  • Модерна конкурентност
  • Богата на възможности и бърза, въпреки абстракциите

Thread pools

  • A work-stealing pool for computation, consisting of exactly the same number of Threads as there are hardware processors (minimum: 2)
  • A single-threaded schedule dispatcher, consisting of a single maximum-priority Thread which dispatches sleeps with high precision
  • An unbounded blocking pool, defaulting to zero Threads and allocating as-needed (with caching and downsizing) to meet demand for blocking operations

https://typelevel.org/cats-effect/docs/schedulers

Ефектни котки…

Cats Effect (демо)

Ефектни котки…

MonadCancel

Разширява MonadError с опция за канселиране. Така ефектът може да бъде в едно от следните състояния:

sealed trait Outcome[F[_], E, A]
final case class Succeeded[F[_], E, A](fa: F[A]) extends Outcome[F, E, A]
final case class Errored[F[_], E, A](e: E) extends Outcome[F, E, A]
final case class Canceled[F[_], E, A]() extends Outcome[F, E, A]

MonadCancel

trait MonadCancel[F[_], E] extends MonadError[F, E]:
  // Gives us a cancelled instance of the effect. Every composition
  // with it should cancel all the dependent effects
  def canceled: F[Unit]

  // Registers an action for when the effect is cancelled
  def onCancel[A](fa: F[A], fin: F[Unit]): F[A]

  // ... and some other methods

MonadCancel (API)

MonadCancel имплементира следната операция:

def bracket[A, B](acquire: F[A])(use: A => F[B])(release: A => F[Unit])

Това ни позволява да менажираме ресурси, които трябва да бъдат затваряни.

  • Първоначално ресурсът се acquire-ва
  • след това го процесваме
  • когато процесването свърши или получим грешка или канселиране, тогава ресурсът се освобождава
  • Повече за това по-късно

Unique

trait Unique[F[_]]:
  def unique: F[Unique.Token]

Генерира гарантирано уникални (при сравнение) стойности

Spawn (API)

trait GenSpawn[F[_], E] extends MonadCancel[F, E] with Unique[F]
  • Позволява конкурентност чрез изпълнение на ефекта в конкурентен fiber – метод start
    • Изпълняват се в compute pool-а
  • Връща IO[Fiber], който може да бъде join-нат, за да изчакаме резултата му, или cancel-иран
    • много често от fiber-а, който го е стартирал
  • Позволявани да изпълним множество ефекти конкурентно (both) или в състезание (race)
  • Не позволява комуникация между fiber-и преди fiber-а да е завършил
    • Можем да извлечем резултат от него само и единствено чрез join
  • parTupled и другите Parallel операции водят до създаване на fiber

Concurrent (API)

Позволява безопасна обмяна на информация между fiber-и

trait GenConcurrent[F[_], E] extends GenSpawn[F, E]:
  // ...

Благодарение на него са имплементирани структури като Ref, Queue, Deferred и други

Spawn vs Concurrent

Clock

trait Clock[F[_]]:
  def monotonic: F[FiniteDuration]

  def realTime: F[FiniteDuration]

Дава ни текущото време

Temporal

Позволява ни да sleep-ваме и да имплементираме timeout-и.

Използва scheduler thread pool-а на Runtime-а.

Sync (API)

Осигурява връзка към външния свят през синхронен API, като отлага неговия страничен ефект:

trait Sync[F[_]] extends MonadCancel[F, Throwable] with Clock[F] with Unique[F] with Defer[F]:
  // Синхронният код, който ще бъде отложен и превърнат
  // във функционален ефект
  def delay[A](thunk: => A): F[A]

  // Блокиращ синхронен код – ще се изпълни в blocking thread pool-а
  def blocking[A](thunk: => A): F[A]

  // Като blocking, но позволяващо канселиране, използващо
  // стандартния метод за канселиране на грешки в Java (interrupt)
  def interruptible[A](thunk: => A): F[A]

Async (API)

Осигурява връзка към външния свят през асинхронен, callback-базиран API:

trait Async[F[_]] extends AsyncPlatform[F] with Sync[F] with Temporal[F]:
  // Позволява на IO да регистрира callback за тази операция
  def async_[A](k: (Either[Throwable, A] => Unit) => Unit): F[A]

  // По-обща версия, позволяваща и канселиране
  def async[A](k: (Either[Throwable, A] => Unit) => F[Option[F[Unit]]]): F[A]

  // Дава достъп до compute execution context-а, в който да се изпълни
  // асинхронен код
  def executionContext: F[ExecutionContext]

  // Подменя comput execution context-а
  def evalOn[A](fa: F[A], ec: ExecutionContext): F[A]

Безопасно управление на ресурси

Задача: имплементация на Channel

Ефектни Type class-ове

  • MonadCancel – добавя възможност за канселиране
  • Spawn – изпълнение на множество конкурентни fiber-а
  • Concurrent – възможност за описване на безопасен достъп до споделени ресурси (т. нар. Ref) и за изчакване на ресурси (т. нар. Deferred)
  • Clock – описване на достъп до текущото време
  • Temporal – приспиване на fiber за определено време
  • Unique – генериране на уникални тоукъни
  • Sync – адаптиране на синхронни изчисления (блокиращи и неблокиращи) към ефектни (и асинхронни) такива
  • Async – адаптиране на callback-базиран асинхронен API към ефектен такъв

Въпроси :)?