val a = 42 // независими
val b = 4 // изчисления
val c = a + b // операция
val d = (a + b) * 10 // композиция на операции
val e = f(g(a)) // композиция на функцииПренесохме възможността за тези операции върху ефекта Future
(и стойността в него)
map – трансформация на единична стойност (напр. val c = -a)map2 (или zipMap) – трансформация на две независими стойности (val c = a + b). Резултатът c зависи от тяхmap3, zipMap3…; mapN дефинира зависимостиflatMap – ефектна трансформация на единична стойностНека да генерализираме тези операции в type class-ове
Ще започнем от една различна гледна точка
Нека имаме функции f: A => B и g: B => C
Тогава h(x) = g(f(x)) е функция от тип A => C
h = g ∘ f
асоциативност – нека f: A => B, g: B => C и h: C => D. Тогава:
(h ∘ g) ∘ f = h ∘ (g ∘ f)неутрален елемент – нека identity = x => x. Тогава ∀ f
identity ∘ f = f ∘ identity = fh ∘ g ∘ f
Функция, връщаща стойност, затворена в ефект
A => Option[B]
A => Future[B]
A => Validated[E, B]
Нека
f: A => Option[B],
g: B => Option[C],
h: C => Option[D]
h ∘ g ∘ f?
За всеки ефект имплементацията е различна
flatMapdef compose[A, B, C, D](f: A => Option[B],
g: B => Option[C],
h: C => Option[D]): A => Option[D] = a => {
val fOption = f(a)
if (fOption != None) {
val gOption = g(fOption.get)
if (gOption != None){
h(gOption.get)
} else {
None
}
} else {
None
}
}
Често срещано при работа с някои езикови елементи (null, callback hell код, …)
trait Monad[F[_]] {
def compose[A, B, C](f: A => F[B], g: B => F[C]): A => F[C]
def unit[A](a: A): F[A]
}
Тук F е конструктор на тип, а не тип
Пример: List е конструктор на тип, List[Int] е тип
F е higher-kinded type (тип от по-висок ред)
higher-kinded polymorphism
асоциативност:
compose(compose(f, g), h) == compose(f, compose(g, h))неутрален елемент
compose(unit, f) == compose(f, unit) == fflatMapflatMap може да се изрази чрез compose като:
unit, map и flatten са трети възможен набор от основни операции
flatMapасоциативност:
Нека m: F[A] и f: A => B, g: B => C. Тогава
m.flatMap(f).flatMap(g) == m.flatMap(a => f(a).flatMap(g))ляв идентитет:
∀a: A и f: A => B е изпълнено: unit(a).flatMap(f) == f(a)десен идентитет:
∀m: F[A] е изпълнено: m.flatMap(unit) == m for в Scala е монадна композицияпреобразува се до:
Функторите могат да бъдат композирани:
В общия случай монадите не могат да се композират. Но много могат
Това води до нуждата от специфични монадни трансформатори
Например OptionT за монади от Option
(тоест M[Option[_]], където M е монада)
mZero: F[A] наричаме нула за монадата F, ако:
∀ f: A → F[B] е изпълнено:
∀m ∈ F[A], която не е нула, е изпълнено: