Конспект по Scala with Cats
Scala with Cats – это свободно доступная книга, написанная Noel Welsh и Dave Gurnell из Underscore. Охватывает все основы использования библиотеки cats.
Книгу рекомендую читать с выполнением заданий, без этого лично мне полного понимания достичь не удалось.
Ниже краткий насколько возможно конспект по Type Class'ам из книги.
Show
- вывести на печать любой тип
- syntax:
.show
Eq
- типобезопасное сравнение
- syntax:
a === b
a =!= b
Monoid
- операции
.combine(a1, a2)
.empty[A]
- законы:
- combine ассоциативный
- empty должен быть нейтральным элементом (identity element)
Semigroup
- объединение двух значений
- только combine из Monoid
- там где требуется Semigroup, всегда можно использовать Monoid
- нужен так как не всегда существует нейтральный элемент, пример – NonEmptyList
- syntax:
a |+| b
Functor
- что-то у чего есть
map
- не стоит думать об этом только, как о проходе по списку, это может быть и работа с внутренним состоянием "контейнера" (в контексте этого "контейнера"), без извлечения значения из "контейнера".
- операции:
.lift(f)
- syntax:
f.map(g)
Contravariant (Functor)
- из
F[B]
имеяA => B
получитьF[A]
- syntax:
.contramap(f)
Invariant
- имея
A => B
,B => A
иMonoid[B]
получитьMonoid[A]
- syntax:
Monoid[B].imap(f)(g)
Monad
- 1000 разных подходов к определению, в книге такое: монада – механизм для последовательных вычислений
- операции
.pure(a)
– создание монадического контекста из сырого значения.flatMap(f)
– извлечение значения из контекста и создание следующего контекста в последовательности
- любая монада – функтор, map легко построить из pure+flatMap
- laws:
- левоассоциативность:
pure(a).flatMap(f) == f(a)
. тут важно помнить об эффектах. именно по этой причине Try не Monad, так как если "снять" с него контекст монады при обычном вызове получим эффект – исключение, а если не снять то получим Failure. - правоассоциативность:
m.flatMap(pure) == m
. - ассоциативность:
m.flatMap(f).flatMap(g) == m.flatMap(x => f(x).flatMap(g))
- левоассоциативность:
- syntax:
.pure[T]
(из cats.syntax.applicative).map(f)
(из cats.syntax.functor).flatMap(f)
(из cats.syntax.flatMap)- можно использовать for comprehensions из scala
- при определении своих монад
.flatMap
.pure
.tailRecM
– оптимизация для вложенных .flatMap вызовов. метод можно делать@tailrec
Id
- Identity Monad
- позволяет использовать в коде, где ожидаются монады немонадные значения
- мощный инструмент для тестов кода построенного на монадах
- например
123 : Id[Int]
"abc" : Id[String]
Either
- обычный тип из scala
- операции:
.orElse(Either)
.ensure(e)(predicate)
.recover(partialFunction)
.recoverWith(partialFunction)
.leftMap(f)
.bimap(f, g)
.swap
- syntax:
- right bias (flatMap/map/filter работающие на правой части Either, есть из коробки в scala 2.12+)
a.asRight
a.asLeft
MonadError
- абстракция поверх Either для работы с ошибками
- параметризирован типом монады и типом ошибки
- операции
.raiseError(e)
.handleError(a)(f)
.ensure(a)(e)(predicate)
- syntax
.ensure(e)(predicate)
Eval
- абстракция над выполнением
- в частности: позволяет делать стекобезопасные рекурсивные вычисления
- операции
.now(f)
.later(f)
.always(f)
.memoize
.defer(=> Eval)
Writer
- сбор лога при вычислении
- операции
val (log, result) = writer.run
.mapWritten(f)
.bimap(f, g)
.mapBoth( (log, res) => ...)
.reset
.swap
- syntax:
a.pure[T]
(требует Monoid[T] в implicit scope)a.tell
a.writer(seq)
Reader
- построение цепочки операций на основе входных параметров
- ex: DI, encoders
- операции:
.flatMap
– позволяет объединить несколько reader от одного входного значения.map
State
- передача дополнительного состояния в вычисления
- моделирование мутабельного состояния в pure functional подходе
State[S, A]
этоS => (S, A)
- операции
val (state, res) = a.run(v).value
a.runS(v).value
a.runA(v).value
- стандартные преобразования
State.get
State.set
State.pure
State.inspect
State.modify
- синтаксис
- for comprehension
Monad transformers
- позволяют комбинировать монады
- комбинация невозможна без знаний об одной из двух монад
- например, EitherT позволяет комбинировать любую монаду с Either
- из коробки:
cats.data.{OptionT, EitherT, ReaderT, WriterT, StateT, IdT}
- соглашение: трансформер определяет внутреннюю монаду, а первый его тип - внешнюю. например,
OptionT[List, A]
– создаст монаду дляList[Option[A]]
- syntax:
- если нужно часто определять alias для типов в стеках монад, поможет scala compiler plugin king projector. с ним можно писать так:
123.pure[EitherT[Option, String, ?]]
.pure
.value
- если нужно часто определять alias для типов в стеках монад, поможет scala compiler plugin king projector. с ним можно писать так:
- использование для реализации:
- многие монады реализованы через трансформеры
type ReaderT[F[_], A, B] = Kleisli[F, A, B]
type Writer[W, A] = WriterT[Id, W, A]
Kleisli
- объединение функций
A => F[B]
,B => F[C]
вA => F[C]
Semigroupal
- объединяет два контекста
- в литературе иногда называют Monoidal
- операции:
.map2
....map22
.tuple2
....tuple22
.contramap2
....contramap22
.imap2
....imap22
- syntax
(a, b, c, ...).tupled
(a, b, c ...).mapN
(Monoid[A], Monoid[B], ...).imapN(toF)(fromF)
- Intellij Idea не понимает тип выражения после mapN, это недавно поправили
- Иногда получаемый результат не сразу очевиден. Например Semigroupal от двух списков будет прямое произведение (каждый с каждым), а не zip списков.
Validated
- аналог Either, но с накоплением всех ошибок
- требует Semigroup для типа ошибки
- операции
Validated.valid[E, A](a)
Validated.invalid[E, A](e)
Validated.catchOnly[Throwable](f)
Validated.catchNonFatal(f)
Validated.fromTry(a)
Validated.fromEither[E, A](a)
Validated.fromOption[E, A](a, e)
.map(f)
.leftMap(f)
.bimap(f, g)
.toEither
.withEither(f)
.withValidated(f)
.getOrElse(a)
.fold(f, g)
- syntax
a.valid[E]
a.invalid[A]
.pure[Validated[F[E], A]]
.raiseError[Validated[F[E], A], E]
.tupled
Apply
- Semigroupal + Functor
- альтернативный к Semigroupal способ закодировать объединение контекстов
- операции
.ap(f)(a)
.product(a, b)
Applicative
- Apply + pure, который позволяет создать новый Applicative instance из сырого значения
- Monad = Applicative + FlatMap
- операции
.pure(a)
NonEmpty*
- аналоги стандартных коллекций, но имеющие строго один или более элементов
NonEmptyList
NonEmptyVector
Foldable
- абстракция для foldLeft / foldRight
- операции
.foldLeft(a, i)(f)
.foldRight(a, i: Eval[B])(f): Eval[B]
(stack safe)- привычные методы коллекций, поверх foldLeft:
.find
,.exists
,.forall
,.toList
,.isEmpty
,.nonEmpty
... .combineAll(a)
(aka.fold
, требует Monoid).foldMap(a)(f)
(требует Monoid).compose(other)
- syntax:
- все операции доступны как синтаксис
a.foldLeft(i)(f)
и т. д.
Traverse
- имея набор из F[A], получить F от набора А
- например,
List[Future[A]]
→Future[List[A]]
- операции
.traverse(a)(f)
.sequence(a)
- syntax:
a.traverse(f)
a.sequence