Композиция
cтрелок
и
морфизмов
симметричных моноидальных категорий
в Scala
Моноид
trait Monoid[A] {
def neutral: A
def combine(x: A, y: A): A
}
Категория
trait Cat[->[_, _]] {
def id[A]: A -> A
def compose[A, B, C](f: B -> C, g: A -> B): A -> C
implicit def homEq[A, B]: Eq[A -> B] = Eq.fromUniversalEquals
class CatLaws {
def left_unit[A, B](f: A -> B) =
(id[B] o f) === f
def right_unit[A, B](f: A -> B) =
(f o id[A]) === f
def associativity[A, B, C, D](f: A -> B, g: B -> C, h: C -> D) =
(h o (g o f)) === ((h o g) o f)
}
implicit class CatHomOps[A, B](f: A -> B) {
def o[C](g: C -> A): C -> B = compose(f, g)
def >>[C](g: B -> C): A -> C = compose(g, f)
}
}
Изоморфизм
case class <->[A, B](to: A -> B, from: B -> A) {
def section = compose(to, from) === id
def retraction = compose(from, to) === id
}
Моноидальная категория
trait MonoidalCat[->[_, _], x[_, _], I] extends Cat[->] {
def left_unit[A]: (I x A) <-> A
def right_unit[A]: (A x I) <-> A
def assoc[A, B, C]: (A x (B x C)) <-> ((A x B) x C)
def tensor[A, B, C, D](f: A -> B, g: C -> D): (A x C) -> (B x D)
class MonoidalLaws {
def pentagon[A, B, C, D] =
((ar[A, B, C] x id[D]) >> ar[A, B x C, D] >> (id[A] x ar[B, C, D])) ===
(ar[A x B, C, D] >> ar[A, B, C x D])
def triagonal[A, B] =
ar[A, I, B] === ((right_unit[A].to x id[B]) >> (id[A] x left_unit[B].from))
def tenson_dist[A, B, C, D, E, F]
(f: A -> B, g: B -> C, h: D -> E, i: E -> F) =
((f x h) >> (g x i)) === ((f >> g) x (h >> i))
def tensor_id[A, B] = (id[A] x id[B]) === id[A x B]
}
def ar[A, B, C] = assoc[A, B, C].from
implicit class TensorHomOps[A, B](f: A -> B) {
def x[C, D](g: C -> D): (A x C) -> (B x D) = tensor(f, g)
}
}
Симметричная моноидальная категория
trait Symon[->[_, _], x[_, _], I] extends MonoidalCat[->, x, I] {
def swap[A, B]: (A x B) -> (B x A)
def symmetry[A, B]: (A x B) <-> (B x A) = <->(swap, swap)
class SymonLaws {
def unit_coherence[A] =
swap[A, I] === (left_unit[A].from o right_unit[A].to)
def assoc_coherence[A, B, C] =
((swap[A, B] x id[C]) >> ar[B, A, C] >> (id[B] x swap[A, C])) ===
(ar[A, B, C] >> swap[A, B x C] >> ar[B, C, A])
}
}
Декартова категория
trait Cartesian[->[_, _], x[_, _], I] extends Symon[->, x, I] {
def proj1[A, B]: (A x B) -> A
def proj2[A, B]: (A x B) -> B
def product[A, B, C](f: A -> B, g: A -> C): A -> (B x C)
def term[A]: A -> I
class CartesianLaws {
def lcomp[A, B, C](f: A -> B, g: A -> C) =
(proj1 o product(f, g)) === f
def rcomp[A, B, C](f: A -> B, g: A -> C) =
(proj2 o product(f, g)) === g
}
}
Замкнутая категория
trait Closed[->[_, _], x[_, _], ==>[_, _], I] extends Symon[->, x, I] {
def lcurry[A, B, C](p: (A x B) -> C): A -> (B ==> C)
def luncurry[A, B, C](p: A -> (B ==> C)): (A x B) -> C
class ClosedLaws{
def curryEq[A, B, C](p: (A x B) -> C) = luncurry(lcurry(p)) === p
def uncurryEq[A, B, C](p: A -> (B ==> C)) = lcurry(luncurry(p)) === p
}
def lapply[A, B]: ((A ==> B) x A) -> B = luncurry(id)
def rapply[A, B]: (A x (A ==> B)) -> B = runcurry(id)
def lunapply[A, B]: A -> (B ==> (A x B)) = lcurry(id)
def runapply[A, B]: B -> (A ==> (A x B)) = rcurry(id)
def ident[A]: I -> (A ==> A) = lcurry(left_unit[A].to)
def choose[A]: A -> (I ==> A) = lcurry(right_unit[A].to)
def unchoose[A]: (I ==> A) -> A =
compose(lapply[I, A], right_unit[I ==> A].from)
}
trait CartesianClosed[->[_, _], x[_, _], ==>[_, _], I]
extends Cartesian[->, x, I] with Closed[->, x, ==>, I]
Лямбда исчисление
\frac{} {\Gamma \vdash x: T} x : T \in \Gamma
\frac
{\Gamma \vdash f : A \to B ~~~~ \Gamma \vdash x: A}
{\Gamma \vdash f(x) : B}
Variable
Application
\frac
{\Gamma, x: A \vdash e: B}
{\Gamma \vdash (\lambda x: A. e) : A \to B}
Abstraction
Лямбда исчисление
name: UUID \to String, \\
age: UUID \to Int, \\
user: String \to Int \to User, \\
d: Int\\
\vdash \\
\lambda\, uuid: UUID . \\ user(name\, uuid) (age\, uuid) \\ : UUID \to User
Lambek Horrespondence
\frac{} {A \vdash A} id
\frac{} {A \times B \vdash A} proj_1
\frac{} {A \times B \vdash B} proj_2
\frac{} {A \vdash \top} term
\frac{A \vdash B ~ B \vdash C } {A \vdash C} compose
\frac{\Gamma \vdash A ~ \Gamma \vdash B} {Г \vdash A \times B} product
\frac {A \times B \vdash C} {A \vdash B \to C} curry
\frac {A \vdash B \to C} {A \times B \vdash C} uncurry
Пример:
def name(id: UUID): String
def balance(id: UUID): BigDecimal
def plus(x: BigDecimal, y: BigDecimal): BigDecimal
def user(name: String, balance: BigDecimal): User
def total(main: UUID, secondary: UUID): User =
user(name(main), plus(balance(main), balance(secondary)))
def name: UUID -> String
def balance: UUID -> BigDecimal
def plus: (BigDecimal x BigDecimal) -> BigDecimal
def user: (String x BigDecimal) -> User
def total: (UUID x UUID) -> User =
(product(name, balance) x balance) >> ar >> (id[String] x plus) >> user
Функции
def getName(id: UUID): String
def getBalance(id: UUID): BigDecimal
def makeUser(name: String, balance: BigDecimal): User
def plus(x: BigDecimal, y: BigDecimal): BigDecimal
def total(main: UUID, secondary: UUID) = {
val name = getName(main)
val mainBalance = getBalance(main)
val secondaryBalance = getBalance(main)
val totalBalance = plus(mainBalance, secondaryBalance)
makeUser(name, totalBalance)
}
Функции
def let[A, B](x: A)(cont: A => B): B = cont(x)
def getName: UUID => String
def getBalance: UUID => BigDecimal
def plus: BigDecimal => BigDecimal => BigDecimal
def makeUser: String => BigDecimal => User
def total: UUID => UUID => User = main => secondary =>
let(getName(main))(name =>
let(getBalance(main))(mainBalance =>
let(getBalance(secondary))(secondaryBalance =>
let(plus(mainBalance)(secondaryBalance))(totalBalance =>
makeUser(name)(totalBalance)
))))
Монады
def getName: UUID => F[String]
def getBalance: UUID => F[BigDecimal]
def plus: BigDecimal => BigDecimal => F[BigDecimal]
def makeUser: String => BigDecimal => F[User]
def total: UUID => UUID => F[User] = main => secondary =>
getName(main) flatMap (name =>
getBalance(main) flatMap (mainBalance =>
getBalance(secondary) flatMap (secondaryBalance =>
plus(mainBalance)(secondaryBalance) flatMap (totalBalance =>
makeUser(name)(totalBalance)
))))
For
def getName(id: UUID): F[String]
def getBalance(id: UUID): F[BigDecimal]
def makeUser(name: String, balance: BigDecimal): F[User]
def plus(x: BigDecimal, y: BigDecimal): F[BigDecimal]
def total(main: UUID, secondary: UUID): F[User] =
for {
name <- getName(main)
mainBalance <- getBalance(main)
secondaryBalance <- getBalance(main)
totalBalance <- plus(mainBalance, secondaryBalance)
} yield makeUser(name, totalBalance)
Монады
sealed trait ConsoleM[X]
object ConsoleM {
case class Pure[A](x: A) extends ConsoleM[A]
case class Bind[A, B](x: ConsoleM[A], fab: A => ConsoleM[B]) extends ConsoleM[B]
case object GetLine extends ConsoleM[String]
case class PutLine(s: String) extends ConsoleM[Unit]
implicit val monad: Monad[ConsoleM] = new StackSafeMonad[ConsoleM] {
def pure[A](x: A): ConsoleM[A] = Pure(x)
def flatMap[A, B](fa: ConsoleM[A])(f: A => ConsoleM[B]): ConsoleM[B] = Bind(fa, f)
}
val getLine: ConsoleM[String] = GetLine
def echo2: ConsoleM[Unit] = for (x <- getLine; y <- getLine) yield x + y
def countGets[A](cm: ConsoleM[A]): Int = cm match {
case Pure(_) => 0
case GetLine => 1
case PutLine(_) => 0
case Bind(m, f) => countGets(m) + (??? : Int)
}
}
Аппликативные функторы
sealed trait ConsoleA[X]
object ConsoleA {
case class Pure[A](x: A) extends ConsoleA[A]
case class Ap[A, B](f: ConsoleA[A => B], x: ConsoleA[A]) extends ConsoleA[B]
case object GetLine extends ConsoleA[String]
case class PutLine(s: String) extends ConsoleA[Unit]
implicit val applicative: Applicative[ConsoleA] = new Applicative[ConsoleA] {
def pure[A](x: A): ConsoleA[A] = Pure(x)
def ap[A, B](ff: ConsoleA[A => B])(fa: ConsoleA[A]): ConsoleA[B] = Ap(ff, fa)
}
def countGets[X](ca: ConsoleA[X]): Int = ca match {
case Pure(_) => 0
case GetLine => 1
case PutLine(_) => 0
case Ap(f, x) => countGets(f) + countGets(x)
}
def echo2: ConsoleA[Unit] = ???
}
Стрелки
trait Arr[->[_, _]] extends Cartesian[->, (?, ?), Unit] {
def lift[A, B](f: A => B): A -> B
def split[A, B, C, D](f: A -> B, g: C -> D): (A, C) -> (B, D)
def compose[A, B, C](f: B -> C, g: A -> B): A -> C
def proj1[A, B]: (A, B) -> A = lift(_._1)
def proj2[A, B]: (A, B) -> B = lift(_._2)
def term[A]: A -> Unit = lift(_ => ())
def id[A]: A -> A = lift(a => a)
def product[A, B, C](f: A -> B, g: A -> C): A -> (B, C) =
compose(split(f, g), lift(a => (a, a)))
override def tensor[A, B, C, D](f: A -> B, g: C -> D): (A, C) -> (B, D) =
split(f, g)
}
import cats.arrow.Arrow
Стрелки
sealed trait ConsoleArr[X, Y]
object ConsoleArr{
case class Lift[A, B](f: A => B) extends ConsoleArr[A, B]
case class AndThen[A, B, C](start: ConsoleArr[A, B], next: ConsoleArr[B, C])
extends ConsoleArr[A, C]
case class Split[A, B, C, D](first: ConsoleArr[A, B], second: ConsoleArr[C, D])
extends ConsoleArr[(A, C), (B, D)]
case object GetLine extends ConsoleArr[Unit, String]
case object PutLine extends ConsoleArr[String, Unit]
implicit val arrow: Arrow[ConsoleArr] = new Arrow[ConsoleArr] {
def lift[A, B](f: A => B): ConsoleArr[A, B] = Lift(f)
def first[A, B, C](fa: ConsoleArr[A, B]): ConsoleArr[(A, C), (B, C)] = Split(fa, id)
def compose[A, B, C](f: ConsoleArr[B, C], g: ConsoleArr[A, B]): ConsoleArr[A, C] = AndThen(g, f)
}
val getLine: ConsoleArr[Unit, String] = GetLine
val putLine: ConsoleArr[String, Unit] = PutLine
def concat: ConsoleArr[(String, String), String] = Lift(tupled(_ + _))
Стрелки
def echo2: ConsoleArr[Unit, Unit] =
(getLine &&& getLine) >>> concat >>> putLine
def countGets[X, Y](carr: ConsoleArr[X, Y]): Int = carr match {
case Lift(_) => 0
case AndThen(start, next) => countGets(start) + countGets(next)
case Split(first, second) => countGets(first) + countGets(second)
case GetLine => 1
case PutLine => 0
}
Capabilities
Задача | Applicative | Monad | Arrow |
---|---|---|---|
последовательная композиция | Х | Monad | Arrow |
условное выполнение | Selective | Monad | ArrowChoice |
сумма результатов | Alternative | MonadPlus | ArrowPlus |
циклы | X | Monad | ArrowLoop |
абстракция и применение | Х | Monad | ArrowApply |
параллельное выполнение | Applicative | Parallel | Arrow |
Применение
- Явное совмещение последовательной и параллельной семантик
- Производительность https://github.com/traneio/arrows
- интроспеция
- структурная оптимизация
- строгость
def echo2: ConsoleArr[Unit, Unit] =
(getLine &&& getLine) >>> concat >>> putLine
def echo2s: ConsoleArr[Unit, Unit] = arr[ConsoleArr] { () =>
val s1 = getLine()
val s2 = getLine()
val s = concat(s1, s2)
putLine(s)
}
Volga
Arrow and
Symmetric Monoidal Category composition syntax helper
import volga.syntax._
val getLine: ConsoleArr[Unit, String] = GetLine
val putLine: ConsoleArr[String, Unit] = PutLine
val concat: ConsoleArr[(String, String), String] = Lift(tupled(_ + _))
val show: ConsoleArr[Int, String] = Lift(_.toString)
val plus: ConsoleArr[(Int, Int), Int] = Lift(tupled(_ + _))
Volga
val foo: ConsoleArr[(Int, Int), Unit] = arr[ConsoleArr] {
(x, y) =>
val xs = show(x)
val ys = show(y)
putLine(xs)
putLine(ys)
val z = plus(x, y)
val zs = show(z)
val s = concat(xs, ys)
val t = concat(zs, s)
putLine(zs)
putLine(s)
putLine(t)
}
Volga
val foo: ConsoleArr[(Int, Int), Unit] = arr[ConsoleArr] { (x: V[Int], y: V[Int]) =>
val xs: V[String] = show(x)
val ys: V[String] = show(y)
putLine(xs)
putLine(ys)
val z: V[Int] = plus(x, y)
val zs: V[String] = show(z)
val s: V[String] = concat(xs, ys)
val t: V[String] = concat(zs, s)
putLine(zs)
putLine(s)
putLine(t)
}
Volga
val foo: ConsoleArr[(Int, Int), Unit] = arr[ConsoleArr] { (x: V[Int], y: V[Int]) =>
val xs: V[String] = ArrSyn(show).apply(x)
val ys: V[String] = ArrSyn(show).apply(y)
ArrSyn(putLine).apply(xs)
ArrSyn(putLine).apply(ys)
val z: V[Int] = ArrSyn(plus)(x, y)
val zs: V[String] = ArrSyn(show)(z)
val s: V[String] = ArrSyn(concat).apply(xs, ys)
val t: V[String] = ArrSyn(concat).apply(zs, s)
ArrSyn(putLine).apply(zs)
ArrSyn(putLine).apply(s)
ArrSyn(putLine).apply(t)
}
Volga
(x, y) =>
val xs = show(x)
val ys = show(y)
putLine(xs)
putLine(ys)
val z = plus(x, y)
val zs = show(z)
val s = concat(xs, ys)
val t = concat(zs, s)
putLine(zs)
putLine(s)
putLine(t)
Volga
(x,y) <- <<REUSE>> -< (x,y)
(xs) <- show -< (x)
(ys) <- show -< (y)
<<BREAK>>
(xs,ys) <- <<REUSE>> -< (xs,ys)
() <- putLine -< (xs)
() <- putLine -< (ys)
(z) <- plus -< (x,y)
<<BREAK>>
(zs) <- show -< (z)
(s) <- concat -< (xs,ys)
<<BREAK>>
(t) <- concat -< (zs,s)
() <- putLine -< (zs)
() <- putLine -< (s)
<<BREAK>>
(last$macro$1) <- putLine -< (t)
(x, y) =>
val xs = show(x)
val ys = show(y)
putLine(xs)
----
putLine(ys)
val z = plus(x, y)
val zs = show(z)
----
val s = concat(xs, ys)
val t = concat(zs, s)
putLine(zs)
putLine(s)
----
putLine(t)
Volga
(x,y) <- <<REUSE>> -< (x,y)
(xs) <- show -< (x)
(ys) <- show -< (y)
<<BREAK>>
(ys,x,y,xs) <- <<REUSE>> -< (ys,x,y,xs)
() <- putLine -< (xs)
<<BREAK>>
(xs,ys) <- <<REUSE>> -< (xs,ys)
() <- putLine -< (ys)
(z) <- plus -< (x,y)
<<BREAK>>
(xs,ys) <- <<REUSE>> -< (xs,ys)
(zs) <- show -< (z)
<<BREAK>>
(zs) <- <<REUSE>> -< (zs)
(s) <- concat -< (xs,ys)
<<BREAK>>
(t) <- concat -< (zs,s)
() <- putLine -< (zs)
() <- putLine -< (s)
<<BREAK>>
(last$macro$1) <- putLine -< (t)
liftf[volga.pres.ConsoleArr,
(Int, Int),
(((Int, Int), Int), Int)] {
case (x, y) => (((x, y), x), y)
}.andThen(ident[volga.pres.ConsoleArr, (Int, Int)]
.split(show)
.split(show))
.andThen(
syntax.liftf[volga.pres.ConsoleArr,
(((Int, Int), String), String),
((String, Int, Int, String), String)] {
case (((x, y), xs), ys) => ((ys, x, y, xs), xs)
})
.andThen(ident[volga.pres.ConsoleArr,
(String, Int, Int, String)].split(putLine))
.andThen(
syntax.liftf[volga.pres.ConsoleArr,
((String, Int, Int, String), Unit),
(((String, String), String), (Int, Int))] {
case ((ys, x, y, xs), ()) => (((xs, ys), ys), (x, y))
})
.andThen(ident[volga.pres.ConsoleArr, (String, String)]
.split(putLine)
.split(plus))
.andThen(syntax.liftf[volga.pres.ConsoleArr,
(((String, String), Unit), Int),
((String, String), Int)] {
case (((xs, ys), ()), z) => ((xs, ys), z)
})
.andThen(ident[volga.pres.ConsoleArr, (String, String)]
.split(show))
.andThen(syntax.liftf[volga.pres.ConsoleArr,
((String, String), String),
(String, (String, String))] {
case ((xs, ys), zs) => (zs, (xs, ys))
})
.andThen(ident[volga.pres.ConsoleArr, String].split(concat))
.andThen(
syntax.liftf[volga.pres.ConsoleArr,
(String, String),
(((String, String), String), String)] {
case (zs, s) => (((zs, s), zs), s)
})
.andThen(concat.split(putLine).split(putLine))
.andThen(syntax.liftf[volga.pres.ConsoleArr,
((String, Unit), Unit),
String] {
case ((t, ()), ()) => t
})
.andThen(putLine)
Volga
Haskell ArrowDo
Cons
- Не добавляет параллельность ни автоматически, ни вручную
- Ещё более нагруженный синтаксис, чем do
Pros
- Автоматическая работа с MonadPlus
Симметричные моноидальные категории
- volga.syntax.arr - Стрелки
- volga.syntax.symon - СМК
Симметричные моноидальные категории
- Не позволяют переносить функции
- Не позволяют переиспользовать результат
- Не позволяют выбрасывать результат
- Единица может быть получена и выброжена
Симметричные моноидальные категории
Симметричные моноидальные категории
- Реактивные потоки
- Протоколы распределённых приложений
- serverless
- Языки запросов СУБД
- Бизнес-правила
- (ко) Эффекты
Будущее
- volga.syntax.arr - Стрелки
- volga.syntax.symon - СМК
- volga.syntax.cc - Декартовы категории
- volga.syntax.smcc - Замкнутые категории
- volga.syntax.сcc - Декартово-замкнутые категории
Будущее - условные конструкции
- if (v) arr1(a ,b, c) else arr2(d, e)
(ArrowChoice) - pattern matching?
- *-autonomous category ?
Будущее - типы данных ?
- Алгебраические типы данных?
- Алгебраические функторы ?
- (ко) индуктивные типы ?
Будущее - (ко) моноиды
trait MonoidSMC[A, ->[_, _], x[_, _], I]{
def zero: I -> A
def combine: (A x A) -> A
}
trait ComonoidSMC[A, ->[_, _], x[_, _], I]{
def drop: A -> I
def duplicate: A -> (A x A)
}
var logging: Logging = _
logging = action1(a, b)
logging = action2(c, d, e)
Спасибо за внимание!
email: odomontois@gmail.com, o.nizhnikov@tinkoff.ru
telegram: @odomontois
asymmon
By Oleg Nizhnik
asymmon
- 1,009