scalabook

Форк
0
279 строк · 10.5 Кб

Монада

Формальное определение

Монада (monad) - это Applicative

, который также поддерживает Bind
, ограниченный законами монады.

Монады являются естественным расширением аппликативных функторов, они обеспечивают решение следующей проблемы: если у нас есть значение с контекстом, m a

, как нам применить его к функции, которая принимает нормальное значение a
и возвращает значение с контекстом.

Для Monad

должны соблюдаться следующие законы (+ все законы родителей: Applicative
, Bind
, Apply
, Functor
и т.д.):

  • leftIdentity - первый закон монад гласит, что если мы берем значение, помещаем его в контекст по умолчанию с помощью unit
    и затем передаем его функции с помощью flatMap
    , это то же самое, что просто взять значение и применить к нему функцию: unit(x).flatMap(f) == f(x)
  • rightIdentity - второй закон гласит, что если у нас есть монадическое значение, и мы используем flatMap
    , чтобы передать его для unit
    , результатом будет исходное монадическое значение: fa.flatMap(unit _) == fa
  • ассоциативность (наследуется от Bind
    ): последний закон монад гласит, что когда у нас есть цепочка приложений монадических функций с flatMap
    , не должно иметь значения, как они вложены: fa.flatMap(f).flatMap(g) == fa.flatMap { a => f(a).flatMap(g) }

Определение в виде кода на Scala

trait Monad[F[_]] extends Applicative[F], Bind[F]:
extension [A](fa: F[A])
override def map[B](f: A => B): F[B] =
fa.flatMap(a => unit(f(a)))

Виды монад

Monad с flatMap
и unit

Монада может быть определена с помощью flatMap

и unit
. В этом случае map
и map2
будут определяться так:

trait Monad[F[_]] extends Functor[F]:
def unit[A](a: => A): F[A]
extension [A](fa: F[A])
def flatMap[B](f: A => F[B]): F[B]
def map[B](f: A => B): F[B] =
flatMap(a => unit(f(a)))
def map2[B, C](fb: F[B])(f: (A, B) => C): F[C] =
fa.flatMap(a => fb.map(b => f(a, b)))

Monad с compose
и unit

Монада может быть определена с помощью compose

и unit
. В этом случае flatMap
(и через неё остальные операции) будет определяться так:

trait Monad[F[_]] extends Functor[F]:
def unit[A](a: => A): F[A]
def compose[A, B, C](f: A => F[B], g: B => F[C]): A => F[C]
extension [A](fa: F[A])
def flatMapViaCompose[B](f: A => F[B]): F[B] =
compose[Unit, A, B](_ => fa, f)(())

В этом случае законы Монады примут вид:

  • identities:
    • compose(f, unit) == f
    • compose(unit, f) == f
  • associativity:
    • compose(compose(f, g), h) == compose(f, compose(g, h))

Monad с map
, join
и unit

Монада может быть определена с помощью map

, join
(другое имя - flatten
) и unit
. В этом случае flatMap
и compose
могут быть определены так:

trait Functor[F[_]]:
extension [A](fa: F[A])
def map[B](f: A => B): F[B]
trait Monad[F[_]] extends Functor[F]:
def unit[A](a: => A): F[A]
extension[A] (ffa: F[F[A]])
def join: F[A]
extension [A](fa: F[A])
def flatMapViaJoinAndMap[B](f: A => F[B]): F[B] =
fa.map(f).join
def composeViaJoinAndMap[A, B, C](f: A => F[B], g: B => F[C]): A => F[C] =
a => f(a).map(g).join

Комбинаторы монад

Монада определяет некоторые комбинаторы:

def sequence[A](fas: List[F[A]]): F[List[A]] =
traverse(fas)(identity)
def traverse[A, B](as: List[A])(f: A => F[B]): F[List[B]] =
as.foldRight(unit(List.empty[B]))((a, acc) => f(a).map2(acc)(_ :: _))
def replicateM[A](n: Int, fa: F[A]): F[List[A]] =
sequence(List.fill(n)(fa))
// Kleisli arrows
def compose[A, B, C](f: A => F[B], g: B => F[C]): A => F[C] =
a => f(a).flatMap(g)

Примеры

"Обертка"

case class Id[A](value: A)
given idMonad: Monad[Id] with
override def unit[A](a: => A): Id[A] = Id(a)
extension [A](fa: Id[A]) override def flatMap[B](f: A => Id[B]): Id[B] = f(fa.value)

Option

given optionMonad: Monad[Option] with
override def unit[A](a: => A): Option[A] = Some(a)
extension [A](fa: Option[A])
override def flatMap[B](f: A => Option[B]): Option[B] =
fa match
case Some(a) => f(a)
case None => None

Последовательность

given listMonad: Monad[List] with
override def unit[A](a: => A): List[A] = List(a)
extension [A](fa: List[A]) override def flatMap[B](f: A => List[B]): List[B] = fa.flatMap(f)

Either

given eitherMonad[E]: Monad[[x] =>> Either[E, x]] with
override def unit[A](a: => A): Either[E, A] = Right(a)
extension [A](fa: Either[E, A])
override def flatMap[B](f: A => Either[E, B]): Either[E, B] =
fa match
case Right(a) => f(a)
case Left(e) => Left(e)

Writer - функциональный журнал

case class Writer[W, A](run: () => (W, A))
trait Semigroup[A]:
def combine(x: A, y: A): A
trait Monoid[A] extends Semigroup[A]:
def empty: A
given writerMonad[W](using monoid: Monoid[W]): Monad[[x] =>> Writer[W, x]] with
override def unit[A](a: => A): Writer[W, A] =
Writer[W, A](() => (monoid.empty, a))
extension [A](fa: Writer[W, A])
override def flatMap[B](f: A => Writer[W, B]): Writer[W, B] =
Writer[W, B] { () =>
val (w1, a) = fa.run()
val (w2, b) = f(a).run()
(monoid.combine(w1, w2), b)
}

State - функциональное состояние

case class State[S, +A](run: S => (S, A))
given stateMonad[S]: Monad[[x] =>> State[S, x]] with
override def unit[A](a: => A): State[S, A] =
State[S, A](s => (s, a))
extension [A](fa: State[S, A])
override def flatMap[B](f: A => State[S, B]): State[S, B] =
State[S, B] { s =>
val (s1, a) = fa.run(s)
f(a).run(s1)
}

IO

final case class IO[R](run: () => R)
given ioMonad: Monad[IO] with
override def unit[A](a: => A): IO[A] = IO(() => a)
extension [A](fa: IO[A]) override def flatMap[B](f: A => IO[B]): IO[B] = f(fa.run())

Реализация

Реализация в Cats

import cats.Monad
import cats.instances.option.*
import cats.instances.list.*
val opt1 = Monad[Option].pure(3) // Some(3)
val opt2 = Monad[Option].flatMap(opt1)(a => Some(a + 2)) // Some(5)
val opt3 = Monad[Option].map(opt2)(a => 100 * a) // Some(500)
val list1 = Monad[List].pure(3) // List(3)
val list2 = Monad[List].flatMap(List(1, 2, 3))(a => List(a, a * 10)) // List(1, 10, 2, 20, 3, 30)
val list3 = Monad[List].map(list2)(a => a + 123) // List(124, 133, 125, 143, 126, 153)
1.pure[Option] // Some(1)
1.pure[List] // List(1)

Реализация в ScalaZ

import scalaz.*
import Scalaz.*
// ... Все операции родителей
for { n <- List(1, 2); ch <- List('a', 'b') } yield (n, ch) // List((1,a), (1,b), (2,a), (2,b))
(for { a <- (_: Int) * 2; b <- (_: Int) + 10 } yield a + b)(3) // 19
List(1, 2) filterM { x => List(true, false) } // List(List(1, 2), List(1), List(2), List())

Ссылки:

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.