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 = f
h ∘ 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?
За всеки ефект имплементацията е различна
flatMap
def 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) == f
flatMap
flatMap
може да се изрази чрез 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], която не е нула, е изпълнено: