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
Traversablepure, и есть бинарная операция <*>; с помощью этих двух операций мы можем устраивать свёртку, скажем, списка... ну или дерева, если есть такая возможность, распараллелить, как в 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 - такой аппликативный функтор называется ZipListtrait 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) } }
Setobject 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
nullOption1. nullnull из си, и жила не тужила, пока не появились ещё дженерики, и стало как-то неудобно. До монад далеко, но уже неудобно.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.OptionOption<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.flattenOption с частичными функциями обращаться проще - вместо списка, вместо 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 fList).
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, внезапно выскакивающем неведомо откуда, хоть ты святой водой кропи этот код, а всё равно.