Apply
Формальное определение
Apply
реализует операцию apply
(также встречаются названия join
, sequence
, joinWith
, ap
, applicate
- названия взаимозаменяемы),
которая объединяет F[A]
и F[A => B]
в F[B]
.
Apply
расширяет Functor
и Semigroupal
.
Об apply
можно думать как о своего рода усиленной map
.
В то время как map
берет функцию и функтор и применяет функцию внутри значения функтора,
apply
берет функтор, в котором есть функция, и другой функтор,
извлекает эту функцию из первого функтора, а затем отображает ее на второй.
Apply
должен удовлетворять следующим законам:
- Composition (композиция) -
apply(fbc)(apply(fab)(fa)) == apply(apply(fbc.map((bc: B => C) => (ab: A => B) => bc compose ab))(fab))(fa)
Определение в виде кода на Scala
trait Apply[F[_]] extends Functor[F], Semigroupal[F]:
def apply[A, B](fab: F[A => B])(fa: F[A]): F[B]
def apply2[A, B, C](fa: => F[A], fb: => F[B])(f: (A, B) => C): F[C] =
apply(fa.map(f.curried))(fb)
override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] =
apply2(fa, fb)((_, _))
def lift2[A, B, C](f: (A, B) => C): (F[A], F[B]) => F[C] =
apply2(_, _)(f)
С помощью операций apply
и map
можно реализовать следующие операции:
apply2
, принимающую дваApply
и функцию преобразования из типов этихApply
в третий типC
и возвращающуюApply
отC
.tuple2
, принимающую дваApply
и возвращающуюApply
от кортежа этих типов.lift2
, "поднимающую" функцию(A, B) => C
до функции преобразованияApply
:(F[A], F[B]) => F[C]
Очевидно, эти операции можно дальше расширять на более длинные кортежи: apply3
, apply4
и т.д.
Примеры
"Обертка"
import cats.Id
given Apply[Id] with
override def apply[A, B](fab: Id[A => B])(fa: Id[A]): Id[B] = Id(fab(fa))
extension [A](as: Id[A]) override def map[B](f: A => B): Id[B] = Id(f(as))
Option
given Apply[Option] with
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
extension [A](as: Option[A])
override def map[B](f: A => B): Option[B] =
as match
case Some(a) => Some(f(a))
case None => None
Реализация
Реализация в Cats
import cats.*, cats.data.*, cats.syntax.all.*
Apply[Option].ap({{(_: Int) + 3}.some })(10.some) // Some(13)
Apply[Option].ap({{(_: String) + "hahah"}.some })(none[String]) // None
Apply[Option].ap({ none[String => String] })("woot".some) // None
Реализация в ScalaZ
import scalaz.*
import Scalaz.*
// Наследуются операции от функтора
...
val intToString: Int => String = _.toString
val double: Int => Int = _ * 2
val addTwo: Int => Int = _ + 2
Apply[Option].ap(1.some)(some(intToString)) // Some(1)
Apply[Option].ap(none)(some(double)) // None
Apply[List].ap(List(1,2,3))(List(double, addTwo)) // List(2, 4, 6, 3, 4, 5)
val add2 = ((_:Int) + (_:Int))
Apply[Option].apply2(some(1), some(2))(add2) // Some(3)
Apply[List].tuple2(List(1,2,3), List("a", "b")) // List((1,a), (1,b), (2,a), (2,b), (3,a), (3,b))
Apply[List].lift2 {(_: Int) * (_: Int)} (List(1, 2), List(3, 4)) // List(3, 4, 6, 8)
1.some <*> {(_: Int) + 2}.some // Some(3)
1.some <*> { 2.some <*> {(_: Int) + (_: Int)}.curried.some } // Some(3)
1.some <* 2.some // Some(1)
1.some *> 2.some // Some(2)
(3.some |@| 5.some) {_ + _} // Some(8)
Ссылки: