scalabook

Форк
0
/
applicative.md 
301 строка · 11.4 Кб

Applicative

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

Applicative

расширяет Apply
InvariantApplicative
) и позволяет работать с несколькими «ящиками». Applicative
, дополнительно к операциям Apply
, реализует операцию unit
(другие названия: point
, pure
), оборачивающую значение произвольного типа A
в Applicative
.

Для Applicative

должны соблюдаться следующие законы (помимо законов родительских классов типов):

  • Identity: apply(unit(identity))(fa) == fa
    (unit
    функции идентичности - это идентичность для apply
    )
  • unit(x).map(f) == unit(f(x))
  • fa.map(f) == apply(unit(f))(fa)
  • Homomorphism: apply(unit(f))(unit(x)) == unit(f(x))
    (Другими словами, применение идиоматической функции к unit
    такое же, как и к unit
    обычного применения функции. Точнее, unit
    — это гомоморфизм из A
    в F[A]
    в отношении применения функции.)
  • Interchange: apply(f)(unit(a)) == apply(unit((f: A => B) => f(a)))(f)
    (Этот закон, по сути, говорит о том, что unit
    не разрешено воздействовать на любую реализацию аппликативного функтора. Если один аргумент, который нужно применить, является unit
    , то другой может появиться в любой позиции. Другими словами, не должно иметь значения, когда мы выполняем unit
    .)
  • Left Identity: unit(()).map2(fa)((_, a) => a) == fa
  • Right Identity: fa.map2(unit(()))((a, _) => a) == fa
  • Associativity: fa.product(fb).product(fc) == fa.product(fb.product(fc)).map(assoc)
    , где def assoc[A, B, C](p: (A, (B, C))): ((A, B), C) = p match { case (a, (b, c)) => ((a, b), c) }
  • Naturality: fa.map2(fb)((a, b) => (f(a), g(b))) == fa.map(f).product(fb.map(g))
  • Composition: apply(u, apply(v, w)) == apply(apply(apply(unit(f => g => f compose g), u), v), w)
    (Применить v
    к w
    , а затем применить u
    к этому — то же самое, что применить композицию к u
    , затем v
    , а затем применить составную функцию к w
    .)

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

trait Applicative[F[_]] extends Apply[F], InvariantApplicative[F]:
def unit[A](a: => A): F[A]
override def xunit0[A](a: => A): F[A] = unit(a)
def apply[A, B](fab: F[A => B])(fa: F[A]): F[B]
override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] =
fa.map2(fb)((a, b) => (a, b))
extension [A](fa: F[A])
def map[B](f: A => B): F[B] =
apply(unit(f))(fa)
def map2[B, C](fb: F[B])(f: (A, B) => C): F[C] =
apply(apply(unit(f.curried))(fa))(fb)

Виды Applicative

Applicative с unit
и apply

map2

и map
могут быть выражены так:

trait Applicative[F[_]] extends Functor[F]:
def unit[A](a: => A): F[A]
def apply[A, B](fab: F[A => B])(fa: F[A]): F[B]
extension [A](fa: F[A])
def map2[B,C](fb: F[B])(f: (A, B) => C): F[C] =
apply(apply(unit(f.curried))(fa))(fb)
def map[B](f: A => B): F[B] =
apply(unit(f))(fa)

Applicative с unit
и map2

apply

и map
могут быть выражены так:

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

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

Applicative

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

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[B]()))((a, acc) => f(a).map2(acc)(_ :: _))
def replicateM[A](n: Int, fa: F[A]): F[List[A]] =
sequence(List.fill(n)(fa))
def product[A, B](fa: F[A], fb: F[B]): F[(A,B)] =
fa.map2(fb)((a, b) => (a, b))

Примеры

Tuple applicative

Как и монады, аппликативные функторы замкнуты относительно произведений; поэтому два независимых аппликативных эффекта обычно могут быть слиты в один, их продукт.

given tupleApplicative[F[_]: Applicative, G[_]: Applicative]: Applicative[[X] =>> (F[X], G[X])] with
type FG[A] = (F[A], G[A])
override def unit[A](a: => A): FG[A] = (summon[Applicative[F]].unit(a), summon[Applicative[G]].unit(a))
override def apply[A, B](fab: FG[A => B])(fa: FG[A]): FG[B] =
(summon[Applicative[F]].apply(fab._1)(fa._1), summon[Applicative[G]].apply(fab._2)(fa._2))

Composite Applicative

В отличие от монад, аппликативные функторы также закрыты по композиции; поэтому два последовательно зависимых аппликативных эффекта обычно могут быть объединены в один. Это называется композицией над Applicative

:

given compositeApplicative[F[_]: Applicative, G[_]: Applicative]: Applicative[[X] =>> F[G[X]]] with
override def unit[A](a: => A): F[G[A]] = summon[Applicative[F]].unit(summon[Applicative[G]].unit(a))
override def apply[A, B](fab: F[G[A => B]])(fa: F[G[A]]): F[G[B]] =
val applicativeF = summon[Applicative[F]]
val applicativeG = summon[Applicative[G]]
val tmp: F[G[A] => G[B]] = applicativeF.map(fab)(ga2b => applicativeG.apply(ga2b))
applicativeF.apply(tmp)(fa)

Произведение и композиция позволяют комбинировать идиоматические (аппликативные) вычисления двумя разными способами; Обычно они называются параллельной и последовательной композицией соответственно. Тот факт, что можно составлять аппликативы, и они остаются аппликативными, очень полезен.

"Обертка"

case class Id[A](value: A)
given idApplicative: Applicative[Id] with
override def unit[A](a: => A): Id[A] = Id(a)
override def apply[A, B](fab: Id[A => B])(fa: Id[A]): Id[B] = Id(fab(fa))

Option

given optionApplicative: Applicative[Option] with
override def unit[A](a: => A): Option[A] = Some(a)
override def apply[A, B](fab: Option[A => B])(fa: Option[A]): Option[B] =
(fab, fa) match
case (Some(aToB), Some(a)) => Some(aToB(a))
case _ => None

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

given listApplicative: Applicative[List] with
override def unit[A](a: => A): List[A] = List(a)
override def apply[A, B](fab: List[A => B])(fa: List[A]): List[B] =
fab.flatMap { aToB => fa.map(aToB) }

Either

given eitherApplicative[E]: Applicative[[x] =>> Either[E, x]] with
override def unit[A](a: => A): Either[E, A] = Right(a)
override def apply[A, B](fab: Either[E, A => B])(fa: Either[E, A]): Either[E, B] =
(fab, fa) match
case (Right(fx), Right(a)) => Right(fx(a))
case (Left(l), _) => Left(l)
case (_, Left(l)) => Left(l)

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 writerApplicative[W](using monoid: Monoid[W]): Applicative[[x] =>> Writer[W, x]] with
override def unit[A](a: => A): Writer[W, A] =
Writer[W, A](() => (monoid.empty, a))
override def apply[A, B](fab: Writer[W, A => B])(fa: Writer[W, A]): Writer[W, B] =
Writer { () =>
val (w0, aToB) = fab.run()
val (w1, a) = fa.run()
(monoid.combine(w0, w1), aToB(a))
}

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

case class State[S, +A](run: S => (S, A))
given stateApplicative[S]: Applicative[[x] =>> State[S, x]] with
override def unit[A](a: => A): State[S, A] =
State[S, A](s => (s, a))
override def apply[A, B](fab: State[S, A => B])(fa: State[S, A]): State[S, B] =
State { s =>
val (s0, aToB) = fab.run(s)
val (s1, a) = fa.run(s0)
(s1, aToB(a))
}

Nested

final case class Nested[F[_], G[_], A](value: F[G[A]])
given nestedApplicative[F[_], G[_]](using
applF: Applicative[F],
applG: Applicative[G],
functorF: Functor[F]
): Applicative[[X] =>> Nested[F, G, X]] with
override def unit[A](a: => A): Nested[F, G, A] = Nested(applF.unit(applG.unit(a)))
override def apply[A, B](fab: Nested[F, G, A => B])(fa: Nested[F, G, A]): Nested[F, G, B] =
val curriedFuncs: G[A => B] => G[A] => G[B] = gaTob => ga => applG.apply(gaTob)(ga)
val fgaToB: F[G[A => B]] = fab.value
val fGaToGb: F[G[A] => G[B]] = functorF.map(fgaToB)(curriedFuncs)
val fga: F[G[A]] = fa.value
val fgb: F[G[B]] = applF.apply(fGaToGb)(fga)
Nested(fgb)

IO

final case class IO[R](run: () => R)
given ioApplicative: Applicative[IO] with
override def unit[A](a: => A): IO[A] = IO(() => a)
override def apply[A, B](fab: IO[A => B])(fa: IO[A]): IO[B] = IO(() => fab.run()(fa.run()))

Реализация

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

import cats.*, cats.data.*, cats.syntax.all.*
Applicative[List].pure(1) // List(1)
Applicative[Option].pure(1) // Some(1)

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

import scalaz.*
import Scalaz.*
// ... Все операции родителей
1.point[List] // List(1)
1.η[List] // List(1)

Ссылки:

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

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

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

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