http://pcapriotti.github.io/assets/applicative.pdf
FreeAL f a = PureL a | ∀b.FreeAL f (b → a) :*: f b
FreeAL f a = PureL a | ∀b.FreeAL f (b → a) :*: f b
for {(name, sDob, membernumber, claimnumber) <- Result.zip(props @ Name, props @ Dob, props @ MemberId, props @ ClaimNr) dob <- parseDob(sDob) stuff <- buildStuff(name, dob, membernumber, claimnumber) } yield stuff
Traversable
pure
, и есть бинарная операция <*>
; с помощью этих двух операций мы можем устраивать свёртку, скажем, списка... ну или дерева, если есть такая возможность, распараллелить, как в map/reduce. trait Traversable
как раз обобщает список и дерево - по нему можно итерировать аппликативный функтор.// T будет траверсабельным, если определены: trait Traversable[T[_]] { // для аппликативного функтора app и функции f обход структуры as def traverse[A, B, F[_]](app: Applicative[F])(f: A => F[B])(as: T[A]): F[T[B]] // и для, скажем, дерева аппликативов можно построить один аппликатив с деревом внутри def dist[B, F[_]](app: Applicative[F]) = traverse[F[B], B, F](app)(identity[F[B]]) _ }
implicit object TraversableList extends Traversable[List] { def cons[A](a: A)(as: List[A]): List[A] = a :: as def traverse[A, B, F[_]](app: Applicative[F])(f: A => F[B])(al: List[A]): F[List[B]] = { al match { case Nil => app.pure(List[B]()) case head :: tail => app.applicable(app.lift(cons[B] _) <@> f(head)) <*> traverse[A, B, F](app)(f)(tail) } } }
pure
, а иначе сканируем весь список, применяя последовательно <*>
.trait Tree[T] case class Leaf[T](t: T) extends Tree[T] def leaf[T](t: T): Tree[T] = Leaf(t) case class Node[T](left: Tree[T], right: Tree[T]) extends Tree[T] def node[T](left: Tree[T])(right: Tree[T]): Tree[T] = Node(left, right)
Leaf
и Node
, но мне было лень возиться с вариантностью дженериков высших типов в определениях... будем проще.implicit object TraversableTree extends Traversable[Tree] { def traverse[A, B, F[_]](app: Applicative[F])(f: A => F[B])(at: Tree[A]): F[Tree[B]] = at match { case Leaf(a) => app.lift(leaf[B]) <@> f(a) case Node(left, right) => { implicit def applicable[A, B](tf: F[A => B]) = app.applicable(tf) val traverse1: (Tree[A]) => F[Tree[B]] = traverse(app)(f) app.pure(node[B] _) <*> traverse1(left) <*> traverse1(right) } } }
traverse1
, а то Скала в типах запутается. И так всё сложно.Env
, попробуйте-ка его траверсировать с помощью Maybe
- это было бы равносильно получению тотальной функции из частичной. Good luck.import TraversableList._ val distributor = dist[String, Set](AppSet) val setOfLists = distributor(List(Set("a", "b"), Set("x", "y"))) setOfLists must_== Set(List("a", "x"), List("a", "y"), List("b", "x"), List("b", "y"))
import TraversableTree._ val distributor = dist[String, List](AppList) val treeOfLists:Tree[List[String]] = Node(Leaf(List("a", "b")), Node(Leaf(List("x", "y", "z")), Leaf(List("1")))) val listOfTrees = distributor(treeOfLists) def tree(s: String) = Node(Leaf("" + s(0)), Node(Leaf("" + s(1)), Leaf("" + s(2)))) listOfTrees must_== List(tree("ax1"), tree("ay1"), tree("az1"), tree("bx1"), tree("by1"), tree("bz1"))
Env
)Map[String, Int]
- имена переменных будут строками, а значения целыми. Не убавляя общности.type Env = Map[String, Int] // хватило бы String=>Int, но для вящей демонстративности... type Environmental[X] = Env => X trait EnvFunctor extends Functor[Environmental] { override def f1[A, B](f: A => B) = (aa: Environmental[A]) => aa andThen f }
val fetch = (varName: String) => (env: Env) => env(varName) val const = (value: Int) => (env: Env) => value val add = (i: Int) => (j: Int) => i + j
fetch
- это константа (типа функция; можете считать референсом, как в Си); fetch("x")
- результат"x"
, т.е. функция, которая берёт Env
и извлекает оттуда"x"
; fetch("x")(Map("a" -> 42, "x" -> "77", "y" -> 2012))
возвратит 77.pure
и <*>
, напишем, как должна выглядетьtrait Expr { def eval: Env => Int } case class Var(name: String) extends Expr { override def eval = fetch(name) } case class Val(i: Int) extends Expr { override def eval = const(i) } case class Add(p: Expr, q: Expr) extends Expr { override def eval = pure(add) <*> p.eval <*> q.eval }
Var
вычисление состоит в выборке, для Val
вычисление состоит в возврате константы, а для сложенияEnv
); в Си пойнтеры складывать можно, а в Скале нельзя).implicit def K[A](a: A) = (env: Env) => a implicit def ski[Env, A, B](fe: Env => A => B) = new { val S = (ae: Env => A) => (env: Env) => fe(env)(ae(env)) }
implicit
, чтобы в нужном контексте функция типа Env => A => B
, превращалась в нечто, что способно применять комбинаторы.K
берёт значение и возвращает постоянную функцию на Env
. а комбинатор S
применяет функцию к значению.trait AppEnv extends EnvFunctor { def pure[A](a: A): Environmental[A] = K(a) implicit def applicable[A, B](fe: Environmental[A => B]) = new Applicable[A, B, Environmental] { def <*>(fa: Environmental[A]) = fe S fa // aka S(f, a) } }
S
применяется инфиксно, формально никакого значения не имеет, но эстетически приятно.object Expressions extends AppEnv { // эта функция выбирает значение переменной из среды val fetch = (varName: String) => (env: Env) => env(varName) // константа, не зависит от среды val const = (value: Int) => (env: Env) => value // сложение двух чисел val add = (i: Int) => (j: Int) => i + j // абстракция выражения trait Expr { def eval: Env => Int } // выражение, состоящее из переменной case class Var(name: String) extends Expr { override def eval = fetch(name) } // выражение, состоящее из константы case class Val(i: Int) extends Expr { override def eval = const(i) } // выражение, состоящее из суммы двух выражений case class Add(p: Expr, q: Expr) extends Expr { override def eval = pure(add) <*> p.eval <*> q.eval } // пример выражения - это функция, её значение зависит от среды val expr = Add(Var("one"), Add(Var("three"), Val(11))).eval // а вот теперь мы её вычислим на среде expr (Map("one" -> 1, "two" -> 2, "three" -> 3)) must_== 15 }
Maybe
, как брюки, превращаются в функтор, в монаду, в аппликативный функтор... причём, List
чудесным образом мог превращаться сразу в два различных аппликативных функтора, первый обычный (когда сшивая два списка, получаем все возможные пары), а второй странный, когда сшиваем два списка вдоль, как замок молнию; для различения этот второй список, был назван ZipList
- хотя он так-то, как параметризованный класс, ну ничем не отличается.List
можно задать как минимум две версии аппликативности. Первая - берём произведение двух списков и возвращаем список всех пар; вторая, ZipList
, - берём диагональ произведения двух списков, то, что в народе называют zip
- такой аппликативный функтор называется ZipList
trait Functor[T[_]] { self => // mapping on objects of a category type f0[_] = T[_] // mapping on arrows of a category def f1[A, B](f: A => B): T[A] => T[B] // builds a composition of two functors def andThen[U[_]](u: Functor[U]) = new Functor[({type UT[X] = U[T[X]]})#UT] { def f1[A, B](f: A => B): (U[T[A]]) => U[T[B]] = u.f1(self.f1(f)) } }
/** * Functorial features of List type */ trait ListFunctor extends Functor[List] { override def f1[A, B](f: A => B) = (aa: List[A]) => aa map f } /** * Functorial features of Seq type */ trait SeqFunctor extends Functor[Seq] { override def f1[A, B](f: A => B) = (aa: Seq[A]) => aa map f } /** * Functorial features of Set type (covariant version) */ trait SetFunctor extends Functor[Set] { override def f1[A, B](f: A => B) = (sa: Set[A]) => sa map f } /** * Functorial features of Either[X,Y], for the second parameter */ trait RightEitherFunctor[L] extends Functor[({type Maybe[A] = Either[L, A]})#Maybe] { def f1[A, B](f: A => B): Either[L, A] => Either[L, B] = _.right.map(f) }
Seq
в Скале примерно соответствует джавному Iterable
.RightEitherFunctor[L]
параметризован - это означает, что для каждого L
полученный Either[L, _]
является функтором.Env
:type E = Any => Int type Env[X] = E => X trait EnvFunctor extends Functor[Env] { override def f1[A, B](f: A => B) = (aa: Env[A]) => aa andThen f }
X
мы начинаем манипулировать функциями, зависящими от окружения (в данном случае, для простоты, окружение возвращает какие-то целые числа.)trait Applicative[T[_]] extends Functor[T] { self => def pure[A](a: A): T[A] // below are additional enrichments, not exactly pertaining to Applicative def ap[A, B](fs: T[A => B]): (T[A] => T[B]) = (ta: T[A]) => fs <*> ta implicit def applicable[A, B](tf: T[A => B]): Applicable[A, B, T] trait Lifted[A, B, T[_]] { def <@>(ta: T[A]): T[B] } implicit def lift[A, B](fun: A => B) = new Lifted[A, B, T] { def <@>(ta: T[A]) = pure(fun) <*> ta } def andThen[U[_]](u: Applicative[U]) = new Applicative[({type UT[X] = U[T[X]]})#UT] { type UT[X] = U[T[X]] def f1[A, B](f: A => B): (U[T[A]]) => U[T[B]] = u.f1(self.f1(f)) def pure[A](a: A) : U[T[A]] = u.pure(self.pure(a)) implicit def applicable[A, B](utf: U[T[A => B]]) = { val uta2tb: U[(T[A]) => T[B]] = u.f1(self.ap[A, B])(utf) new Applicable[A, B, UT] { def <*>(uta: UT[A]) = u.applicable(uta2tb) <*> uta } } } } trait Applicable[A, B, T[_]] { def <*>(ta: T[A]): T[B] }
object AppList extends ListFunctor with Applicative[List] { override def pure[A](a: A) = List(a) override implicit def applicable[A, B](lf: List[A => B]): Applicable[A, B, List] = { new Applicable[A, B, List] { def <*>(as: List[A]) = (for (f <- lf; a <- as) yield f(a)).toList } } }
"ApplicativeList" should { "combine two lists" in { import applicative.All.AppList._ def pp(i: Int) = (j: Int) => i + j * j val combinations:List[Int] = ap(List(pp(1), pp(2)))(List(10, 20, 30)) combinations must_== List(101, 401, 901, 102, 402, 902) } }
Set
object AppSet extends SetFunctor with Applicative[Set] { override def pure[A](a: A) = Set(a) override implicit def applicable[A, B](ff: Set[A => B]) = { val sf: Set[A => B] = ff new Applicable[A, B, Set] { def <*>(fa: Set[A]) = (for (f <- sf; a <- fa) yield f(a)).toSet } } }
object AppZip extends SeqFunctor with Applicative[Seq] { override def pure[A](a: A): Seq[A] = Stream.continually(a) // повторяем пока кому-нибудь не надоест implicit def applicable[A, B](ff: Seq[A => B]) = { new Applicable[A, B, Seq] { def <*>(az: Seq[A]) = ff zip az map ({ case (f: (A => B), a: A) => f(a) }) // в Скале уже есть зип; применим его } } }
Stream
)."ZipList" should { import All.AppZip._ "be able to zip functions with values" in { def prepend(prefix: String) = (s: String) => prefix + " " + s val result = (List(prepend("a"), prepend("the")) <*> List("quick brown fox jumps over ", "lazy dog ")) result must_== List("a quick brown fox jumps over ", "the lazy dog ") } }
"be able to transpose a matrix" in { trait matrices[A] { // матрицы с абстрактными элементами type matrix = Seq[Seq[A]] val glue: Seq[A => List[A] => List[A]] = pure(cons _) // функция, конкатенирующая элемент и список, применяется к последовательности def transpose(m: matrix): matrix = if (m.isEmpty) Nil else transposeNonempty(m) // приходится различать пустую и непустую, потому что размерность не указывать def transposeNonempty(m: matrix): matrix = { // нетривиальная часть m match { case Nil => pure(Nil) // это на случай, когда непустой вектор склеиваем с пустой матрицей case row :: rows => { // типичный случай - применим клей к row и остатку glue <*> row <*> transposeNonempty(rows) } } } } object m extends matrices[Int] import m._ val m0x0: m.matrix = transpose(List[List[Int]]()).toList m0x0 must_== List() // мы не можем даже выразить m0x1 val m0x1: m.matrix = transpose(List(List[Int]())) m0x1 must_== List() val m2x1: m.matrix = transpose(List(List(1, 2))) m2x1 must_== List(List(1), List(2)) val m1x2: m.matrix = transpose(List(List(1), List(2))).toList m1x2 must_== List(List(1,2)) val m2x2 = transpose(List(List(11, 12), List(21, 22))).take(12).toList m2x2 must_== List(List(11, 21), List(12, 22)) }
Traversable
- мапредьюс на самом деле - опять с моноидом.trait RightEitherFunctor[L] extends Functor[({type Maybe[A] = Either[L, A]})#Maybe] { def f1[A, B](f: A => B): Either[L, A] => Either[L, B] = _.right.map(f) } trait AccumulatingErrors[Bad] { val errorLog: Semigroup[Bad] implicit def acc(err: Bad) = errorLog.acc(err) type Maybe[T] = Either[Bad, T] object App extends Applicative[({type Maybe[A] = Either[Bad, A]})#Maybe] with RightEitherFunctor[Bad] { def pure[A](a: A):Either[Bad, A] = Right[Bad, A](a) implicit def applicable[A, B](maybeF: Maybe[A => B]) = { new Applicable[A, B, Maybe] { def <*>(maybeA: Maybe[A]) = maybeF match { case Left(badF) => maybeA match { case Left(errorA) => Left(badF <+> errorA) case Right(_) => Left(badF) } case Right(f) => maybeA match { case Left(badA) => Left(badA) case Right(a) => Right(f(a)) } } } } } }
trait Applicative[T[_]] extends Functor[T] { def pure[A](a:A):T[A] def ap[A,B](tf:T[A=>B]): T[A] => T[B] def map[A,B](f:A=>B) = ap(pure(f)) } abstract class Applicable[A, B, T[_]](fs: T[A=>B]) { def <*>(at: T[A]):T[B] } abstract class Lifted[A, B, T[_]](f: A=>B) { def <@>(ta:T[A]): T[B] // in Haskell it is <$>; can't use $ in Scala, and can't have lt;&s> either. } trait RichApplicative[T[_]] extends Applicative[T] { implicit def applicable[A,B](tf:T[A=>B]): Applicable[A, B, T] implicit def lift[A,B](f: A=>B) = new Lifted[A, B, T](f) { def <@>(at: T[A]) = ap(pure(f))(at)} } trait Traversable[T[_]] extends RichApplicative[T] { def traverse[A, B](f: A => T[B])(as: List[A]): T[List[B]] = as match { case Nil => pure(Nil) case head::tail => lift(cons[B] _) <@> f(head) <*> traverse(f)(tail) } def dist[A] = traverse(identity[T[A]] _) _ }
def K[env,a](x:a) = (gamma:env) => x implicit def ski[env,a,b](ef:env=>a=>b) = new { val S = (ea:env=>a) => (gamma:env) => ef(gamma)(ea(gamma)) } val add = (i: Int) => (j: Int) => i+j type Env = String => Int def fetch(x: String) = (env: Env) => env(x) trait Expr case class Var(x: String) extends Expr case class Val(i: Int) extends Expr case class Add(p: Expr, q: Expr) extends Expr def Ke[T](x:T) = K[Env, T](x) def eval(exp: Expr): (Env => Int) = exp match { case Var(x) => fetch(x) case Val(i) => Ke(i) case Add(p,q) => (Ke(add) S (eval(p))) S (eval(q)) } eval(Add(Var("one"),Add(Var("three"), Val(11))))(Map("one" -> 1, "two" -> 2, "three" -> 3))
ap
между нашими аппликативными штучками, как это? Я думал, э, всё просто.import scala.collection.immutable._ trait Functor[T[_]]{ def map[A,B](f:A=>B)(ta:T[A]):T[B] } trait Applicative[T[_]] extends Functor[T]{ def pure[A](a:A):T[A] def ap[A,B](tf:T[A=>B])(ta:T[A]):T[B] } implicit def applicablize[A, B](fs: Set[A=>B]) = new { def <*>(as: Set[A]) = (for (f <- fs; a <- as) yield f(a)).toSet } implicit object AppSet extends Applicative[Set] { def pure[A](a: A) = Set(a) def ap[A,B](fs:Set[A=>B])(as:Set[A]):Set[B] = (for (f <- fs; a <- as) yield f(a)).toSet def map[A,B](f:A=>B)(as:Set[A]) = ap(pure(f))(as) } val p = AppSet.pure((s:String) => "'" + s + "'") val p2 = AppSet.pure((first: String) => (second:String) => first + " " + second) val names = Set("Jedi", "Michael Valentine") p <*> names val lasts = Set("Smith", "Frazer") p2 <*> names <*> lasts
def process (user: User) (phone:Phone) = { val s = user.name + ": " + phone.number; println(s); s } val result: Set[String] = AppSet.pure(process) <*> db.getUser(userId) <*> db.getPhones(userId)
db.getSomething(userId)
возвращает не Set[Something]
, а Future[Set[Something]]
- обещание вернуть что-то, когда готово будет. Тогда и результат наш будет иметь формуval result: Future[Set[String]] = AppFuture.pure(AppSet.pure(process)) <*> db.getUser(userId) <*> db.getPhones(userId)
AppFuture.pure(AppSet.pure(process))
; но на это в скале есть неявные функции. Если мы определим implicit def singleton[T](t: T) = AppSet.pure(t) implicit def toFuture[T](t: T) = AppFuture.pure(t)
val result = process <*> db.getUser(userId) <*> db.getPhones(userId)
def ap[A, B](f: F[A => B], a: F[A]): F[B]
def strength[A, B](F[A], F[B]): F[A × B]
A × B
- такого обозначения в Скале нету на самом деле; есть Pair[A, B]
, aka Tuple2[A, B]
- класс пар; ну мы ж все понимаем, что это декартово произведение и есть, и формально надо было написатьdef strength[A, B](Pair[F[A], F[B]]): F[Pair[A, B]]
strength
, то можно определитьdef ap[A, B](ff: F[A => B], fa: F[A]): F[B] = strength(ff, fa).map((f, a) => f(a))
ap
, определимdef pairing[A, B](a: A): (B => Pair[A, B]) = b => (a, b) // функция такая def pairingLifted[A, B](fa: F[A]): (F[B] => F[Pair[A, B]]) = fa map pairing // та же функция, да на F[A] def strength[A, B](fa: F[A], fb: F[B]): F[Pair[A, B]] = ap(pairingLifted, fa) // QED
strentgh
, традиционно называется монадической силой, если речь идёт о монадах. И в компьютерной науке есть поверие, что всякая монада обладает монадической силой - ну просто мы подымем бинарную операцию в монаду, и всё получится.
def process(user: User, phone: Phone) = { val s = user.name + ": " + phone.number; println(s); s } for (user ← db.getUser(userId); // обычно это максимум один юзер phone ← db.getPhones(userId) // телефонов может быть и несколько (драма, если написано на джаве) ) process(user, phone)
select user.name, phone.number from user, phone where user.user.id = phone.userId
process
. Мы её сейчас превратим в функцию одной переменной, возвращающую функцию другой переменной (это карриинг).def process (user: User) (phone:Phone) = { val s = user.name + ": " + phone.number; println(s); s }
process(user)
, то результат будет иметь тип Phone => String
. Зачем бы нам это было нужно? Ну мы могли бы, например, преобразовать наш цикл в такой:for (user ← db.getUser(userId); f = process(user); phone ← db.getPhones(userId); ) f(phone)
val fs = db.getUser(userId).map(process) val phones = db.getPhones(userId) for (f ← fs; phone ← phones) f(phone)
val fs: Set[Phone => String] = db.getUser(userId).map(process) val phones: Set[Phone] = db.getPhones(userId)
for (f ← fs; phone ← phones) f(phone)
ap
:def ap[A, B](f: F[A => B], a: F[A]): F[B]
val fs = db.getUser(userId).map(process) val phones = db.getPhones(userId) val result = fs ap phones
ap
типа Set
, потому что у этого типа есть такой метод, а этот метод у него есть, потому что этот тип содержит trait Applicative
.val fs = db.getUser(userId).map(process)
, но на самом деле мы могли бы выполнить эту операцию с участием того же ap
:val processes = Set(process) ap(processes, db.getUser(userId))
ap
надо где-то определить для конкретного функтора. Так как определять его всё равно надо, можно определить прямо в функторе (в Скале для этого есть трюк, называется pimping, но я его пропущу для простоты).class Set[A]} { ... def ap[B](f: F[A => B], a: F[A]): F[B]
trait Functor[T[_]]{ def map[A,B](f:A=>B)(ta:T[A]):T[B] } trait Applicative[T[_]] extends Functor[T]{ def pure[A](a:A):T[A] def ap[A,B](tf:T[A=>B])(ta:T[A]):T[B] } implicit def applicablize[A, B](fs: Set[A=>B]) = new { def <*>(as: Set[A]) = (for (f <- fs; a <- as) yield f(a)).toSet } implicit object AppSet extends Applicative[Set] { def pure[A](a: A) = Set(a) def ap[A,B](fs:Set[A=>B])(as:Set[A]):Set[B] = (for (f <- fs; a <- as) yield f(a)).toSet def map[A,B](f:A=>B)(as:Set[A]) = ap(pure(f))(as) } val p = AppSet.pure((s:String) => "'" + s + "'") val p2 = AppSet.pure((first: String) => (second:String) => first + " " + second) val names = Set("Jedi", "Michael Valentine") p <*> names val lasts = Set("Smith", "Frazer") p2 <*> names <*> lasts
processes = AppSet.pure(process) processes <*> db.getUser(userId)
val result: Set[String] = AppSet.pure(process) <*> db.getUser(userId) <*> db.getPhones(userId)
process
, строим из неё синглтон, т.е. применяем монадическую единицу, а потом сворачиваем её с юзерами и телефонами. В результате получаем множество искомых строк.map
).System.out.println("Mom, I'm in a function!")
, то это у вас монада. Если вы вставляете String myDrive = new File(".").getAbsolutePath().split(":")[0], то это тоже монада. Если у вас в коде
User user = DaoFactoryManager.getUserDaoFactory().getUserDao().getUserByUserId(userId); if (user != null) ...то это тоже монада, даже две.
livejournal.getFriends(userid: String): Option[Set[String]]
- он возвращает или Some[Set[String]]
, или None
когда жж в дауне, т.е. довольно часто.livejournal.getFriends("ivan_gandhi") match { case None => err.println("ну вот, опять Носик сломал код") case Some(friends) => { println("Это всё мои друзья:"); for (friend <- friends) println(" - " + friend) } }
livejournal
никогда не ошибался, а всегда бы возвращал Set[String]
, то мы ж могли бы выразить это наше действие одним циклом:for (f <- livejournal.getFriends("ivan_gandhi"); fof <- livejournal.getFriends(f) ) println(" - " + fof)
livejournal.getFriends("ivan_gandhi").flatMap(f => livejournal.getFriends(f)).foreach(x => println(" - " + x))
flatMap
.flatMap
- это map
за которым следует flatten
- сначала мы применяем map
, получая, в нашем случае, Set[Set[String]]
, а потом уже сплющиваем Set[Set[String]] → Set[String]
. Сплющивание монады Set
вещь не совсем тривиальная; могут попадаться дубликаты, их игнорируем.Option[Set[Option[Set[String]]]]
. Мы можем получить ошибку при выборке списка наших друзей, и при выборке списка друзей любого из наших друзей, и неоднократно. Что можно предпринять? Можно прописать решение руками, например:for (fOpt <- livejournal.getFriends("ivan_gandhi"); f <- fOpt; fofOpt <- livejournal.getFriends(f); fof <- fofOpt ) println(" - " + fof)
for (stockOpt <- etrade.getPortfolio("vpatryshev", "password1"); stock <- stockOpt; batchOpt <- etrade.getTrades("vpatryshev", "password1", stock); batch <- batch ) println(stock + ": " + batch.size + "@" + batch.purchasePrice)
Set[Set[T]]
, то мы это можем сплющить. А если Set[Option[Set[Option[T]]]]
, то неочевидно. Если бы мы могли это преобразовать в Set[Set[Option[Option[T]]]]
, то можно было бы отдельно плющить Set[Set[...]]
и Option[Option[T]]
. Но проблема в том, что в общем виде такого преобразования не существует. M[N[x]] → N[M[x]]
. Но, разумеется, в каждом конкретном случае можно какое-нибудь такое преобразование выдумать. Такие преобразования называются monad transformers по-английски; как они называются по-русски, я понятия не имею; не трансформеры же, и не трансформаторы же. Буду употреблять слово "преобразование" пока меня не поправят. Если б алгебраисты знали, какой тут катаклизм, они б подсказали - да коммутатор это, коммутатор. Но алгебраисты обычно такой фигнёй не занимаются.Option
. Нам нужно преобразование optionT: Option[M[T]] → M[Option[T]]
, независимо от природы M
. Вот стандартное решение:def optionT[M[_], T](optionalmt: Option[M[T]]) = optionalmt match { case None => M.unit(None) // засунули None внутрь M, получив M[Option[T]] case Some(mt) => mt.map(t => Some(t)) // засунули Some() внутрь M, получив M[Option[T]] }
Set[T]
, и Option[T]
наследуют от Iterable[T]
; поэтому мы фактически сплющиваем Iterable[Iterable[Iterable[Iterable[T]]]]
, получая в конце концов Iterable[T]
; дальше его можно вручную превратить в Set[T]
.List[M[T]] → M[List[T]]
. Но не в общем виде. Поэтому в скале и в хаскеле существуют чуть ли не библиотеки преобразований; а т.к. решение в общем случае довольно произвольно, то нельзя сказать, что это единственно правильные преобразования.Т[X]
(с одним параметром X
); для каждого типа X
мы получаем типа T[X]
. Это преобразование типов можно называть функтором при следующих условиях:f: X → Y
определена функция T(f): T[X] → T[Y]
. Эта операция, превращение f
в T(f)
обычно называется map
(в хаскеле, в скале, в functional java) или transform
(c++, гугловская джавная библиотека Guava).identity[X]: X → X
, то T(identity[X])
будет равна identity[T[X]]
. (Что такое равенство двух функций, я тут лучше не буду обсуждать.)f: X → Y
и g: Y → Z
, и функцию h = f.andThen(g): X → Z
, то T(h) = T(f).andThen(T(g)): T[X] → T[Z]
.Class[T]
не является функтором - тот факт, что имеется функция f: X → Y
не даёт нам никакой функции из Class[X]
в Class[Y]
. List[T]
- по каждому f: X → Y
строится список List[X].map(f)
- т.е. мы имеем ту самую трансформацию f ↦ List[X].map(f)
. ↦
употребляется для обозначения того факта, что каждому значению слева сопоставляется значение справа. Например, функция "синус" даёт следующее сопоставление: x ↦ sin(x)
- каждому x
сопоставляем его синус. В случае, когда мы имеет дело с множествами, совокупность всех таких сопоставлений и задаёт функцию.) u[X]: X → T[X]
, она иногда называется pure
, кой-где называют return
.X
каким-то произвольным образом задали функцию; надо, чтобы она хорошо вела себя по отношению к map
.f: X → Y
, то мы же можем из X
в T[Y]
попасть двумя путями, один через Y
, T[X]
, m[X]: T[T[X]] → T[X]
. Ещё известно под именем join
и flatten
.u[X]
; не буду выписывать.T[T[T[X]]] → T[T[X]] → T[X]
можно выполнить двумя способами; результат должен совпадать. T[X] → T[T[X]] → T[X]
- можно построить две таких функции, и они обе тождественные. List, State, Maybe (Option), Set (ковариантая версия), Environment, Read, Continuation
... В мои планы не входит разбираться с ними со всеми, это отдельная тема. Я пока что возьму четыре, List, Option, Set
, которые в джаве и в скале вполне формализованы, а также exception, тайная монада, из тех, про которые Эрик Мейер говорил, что во всяком языке прячется неявная монада.x ↦ Some(x)
, умножение - сплющивание (Some(Some(x)) ↦ Some(x)
, остальное в None
).Set
, визуально не отличимые. Я сейчас имею в виду вот какой: f: X → Y
, то Set(f)
представляет собой следующую функцию:(s: Set[X]) ↦ Imagef(s) = {(y:Y)| s.contains(f-1(y))}
.s
элементов типа X
сопоставляется его образ под действием f
. Set[Set[T]]
и представляет собой объединение.(ssx: Set[Set[T]]) ↦ ∪{sx | ssx.contains(sx)}
.trait Exceptional[T] { def apply(); } case class Ok[T](value: T) extends Exceptional[T] { def apply = value } case class Oops[T](x: Exception) extends Exceptional[T] { def apply = throw x }
Option[T]
; умножение можно задать примерно так:def m[T](eet: Exceptional[Exceptional[T]]): Exceptional[T] = eet match { case Ok(et) => et // just extract the content which is an Exceptional anyway case ex => ex // it's the same exception
null
Option
1. null
null
из си, и жила не тужила, пока не появились ещё дженерики, и стало как-то неудобно. До монад далеко, но уже неудобно.null
- да никаких проблем, кто только не возвращает null
; и принять null
в качестве параметра тоже милое дело, дескать, нетути этого вашего параметра, ну не шмогла я.null
и если что, возвращают null
; чем не Option
. Да тем, что всё это нужно явно прописывать. String countryName(User user) { if (user == null) return null; String phone = user.getPhone(); if (phone == null) return null; String countryCode = PSTN.extractCountryCode(phone); if (countryCode == null) return null; Country country = Countries.findByCode(countryCode); if (country == null) return null; return country.getName();
String countryName(String userId) { User user; Phone phone; String cc; Country country; return userid != null && (user = db.findUser(userid)) != null && (phone = user.getPhone) != null && (cc = phone.getCountryCode) != null && (country = Countries.findByCode(cc)) != null ? country.getName() : null }
null
, волшебным образом должна вернуть null
, или что метод, вызванный на объекте null
, вернёт null
, а не, к примеру, 0, если хотим длину. Поэтому и пишут методы isEmpty(String s) { return s == null ? 0 : s.isEmpty(); }
.null
куда-то протёк внутрь, в какую-нибудь библиотечную функцию, та дальше передала - и потом разбирайся, в чём вообще дело. В реальности-то в джаве принято возвращать null
на выходе, но не проверять на входе.null
вместо осмысленного значения, и нам надо с него что-то взять, то мы получим NPE
; исключение - более-менее монада, в отличие от null
.String countryName(User user) { try { return Countries.findByCode(PSTN.extractCountryCode(user.getPhone())); } catch (NPE npe) { return null; } }
NPE
на нас не бросится, то вернём "данное", ну а если бросится, так только одно. Может быть, так и надо на джаве писать. NPE
- может, вовсе и не из-за того, что у нас данных нету; для прототипа такой код годится, а в реальной программе вряд ли. Но для прототипа годится.NullUser, у которого есть
NullPhoneу которого есть пустой номер (""), по которому PSTN возвращает пустой код страны (или код пустой страны... хм, я знаю одну пустую страну, называется Атолл Диего Гарсиа - код страны есть, но нет ни одного опубликованного телефона; все на кораблях. Но мы введём пустую страну, NullCountry, и тогда Всё Будет Правильно.
String countryName(User user) { return Countries.findByCode(PSTN.extractCountryCode(user.getPhone())); }
None/NAN/Not_a_Country/Not_a_Phone/Not_a_Pipe
. Практически монада Option, но имплементированная для каждого типа отдельно, вручную; для различения ещё нужно добавить метод boolean isValid()
, который будет возвращать true
для нормальных значений и false
для нашего None.Option
Option<T>
, у которого будет две имплементации, None
и Some(T value)
.T x
значение new Some<T>(x)
- т.е. просто конструктор, иOption<Option<T>>
, то из него получается Option<T>
. Option
из них самая простая. Её и плющить особенно легко: None
превращается в None
, Some(None)
превращается в None
, Some(Some(x))
превращается в Some(x)
.List<List<T>> → List<T>
- в данном случае мы пробегаем по списку списков и возвращаем список всего, что нам попалось по дороге:<T> List<T> flatten(List<List<T>> lol) { List<T> result = new // something for (List<T> list : lol) { for (T element : list) { result.add(element); } } return result; }
def flatten[T] (lol: List[List[T]]): List[T] = { val result = new scala.collection.mutable.// какой-нибудь список подходящего типа for (list <- lol element <- list) result.add(element) result }
def flatten[T] (lol: List[List[T]]) = lol.flatten
Option
с частичными функциями обращаться проще - вместо списка, вместо null
, вместо исключения мы просто возвращаем всегда Option
; а чтобы применить функцию к "содержимому Option
", мы можем использовать цикл:for (User user : db.findUser(userid)) { for (Phone phone : user.getPhone()) { for (String countryCode: phone.getCountryCode()) { for (Country country: Countries.findByCode(countryCode)) { System.out.println("User " + user + " is from " + country); }}}}
Option<T> implements Iterable<T>
- ну это несложно. None всегда возвращает пустой Iterable
, а Some
возвращает одноэлементный Iterable
.for (user <- db.findUser(userid) phone <- user.getPhone cc <- phone.getCountryCode country <- Countries.findByCode(cc) ) println("User " + user + " is from " + country)
fmap
<A,B> B f(A a) {...}
, то монада... ну, скажем, та же Option
, определяет функцию <A, B> Option<B> Option_f(Option<A> aOpt) { for (a : aOpt) { return new Some<B>(f(a)); } return None; }
def Option_f[A,B](aOpt: Option[A]) = { for (a <- aOpt) return Some(f(a)) None }
def Option_f[A,B](aOpt: Option[A]) = aOpt map f
List
).int fact(int n) { return n < 2 ? 1 : n * fact(n-1); }
17
вернёт -288522240
. Разве ж це факториал? Это уже скорее гамма-функция от -0.0000000346593739
, примерно, то есть, (-1.0000000346593739)!
(примерно).fact(13) = 1932053504
даже не делится на 13
; правильный ответ был бы 6227020800
.int fact(int n) { if (n < 0 || n > 12) throw new IllegalArgumentException("won't calculate factorial for " + n + ", should be between 0 and 12"); return n < 2 ? 1 : n * fact(n-1); }
Option
, aka Maybe
. В принципе, вполне моделируется списком или множеством из не более чем одного элемента. Да и в цикле неплохо смотрится:for (int f: factorial(n)) { System.out.println("factorial(" + n + ") = " + f); } List<int> factorial(final int n) { if (n < 0 || n > 12) return Collections.emptyList<Integer>(); return n < 2 ? Arrays.asList(1) : factorial(n-1).map(new Function <Integer, Integer>() { public Integer apply(Integer k) { return n * k; } }); }
List
есть метод map
; такого метода, конечно, нету, поэтому нам тут надо бы переписать без рекурсии, с циклом. Но это неважно; важен принцип.for (f <- factorial(n)) { println("factorial(" + n + ") = " + f); } def factorial(n: Int): List[Int] = { if (n < 0 || n > 12) List() else if (n < 2) List(1) else factorial(n-1) map (x => n * x) }
Option
:def factorial(n: Int): Option[Int] = { if (n < 0 || n > 12) None else if (n < 2) Some(1) else factorial(n-1) map (x => n * x) }
Either
, которое в случае удачи содержит результат, а в случае неудачи содержит информацию об ошибке.if (connection != null)
... if (userId != null)
... if (user != null)...
. Этим говнокодом, как фликр фотографиями кошек, заполнены все гиты, эсвээны, сивиэсы, перфорсы, виэсэсы, и в наибольшей степени - клиеркейсы.NullPointerException
, внезапно выскакивающем неведомо откуда, хоть ты святой водой кропи этот код, а всё равно.