Стрелочная и моноидальная композиция
в 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
}
Изоморфизм
case class <->[A, B](to: A -> B, from: B -> A) {
def section = compose(to, from) === id
def retraction = compose(from, to) === id
}
Моноидальная категория
Симметричная моноидальная категория
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)
}
trait Symon[->[_, _], x[_, _], I] extends MonoidalCat[->, x, I]
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
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
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 e: A}
{\Gamma \vdash f(e) : B}
Variable
Application
\frac
{\Gamma, x: A \vdash e: B}
{\Gamma \vdash (\lambda x: A. e) : A \to B}
Abstraction
\Gamma = \{ a : A, b : B, c : C \}
Lambek Сorrespondence
\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
Монады
trait Monad[F[_]] {
def pure[A](a: A): F[A]
def flatMap[A, B](a: A)(f: A => F[B]): F[B]
}
Используют функции - продолжения для композиции
- Используют все возможности языка
- Непрозрачны
Задача
- Выразить процесс, который может читать и писать строки в консоли
- Процесс должен уметь выполнять произвольные чистые преобразование над обрабатываемыми данными
- Реализовать процесс echo2 : прочитать две строки из консоли и вывести в консоль сконкатенированный результат
- Реализовать функцию countGets, которая определит для любого процесса, сколько раз он прочитает строку из консоли
Попытка 1: Монады
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)
}
}
Попытка 2: Аппликативные функторы
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
}
import cats.arrow.Arrow
Попытка 3: Стрелки
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
}
Попытка 3: Стрелки
Возможности
Задача | 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
Volga
arrow(a, b, c)
(x, y, z)
arrow(a, b, c)
val x = arrow(a, b, c)
val (x, y, z) = arrow(a, b, c)
arr( (x, y, z) =>
Передача параметров
Промежуточные вычисления
Результат
Haskell ArrowDo
Cons
- Не добавляет параллельность ни автоматически, ни вручную
- Ещё более нагруженный синтаксис, чем do
Pros
- Автоматическая работа с MonadPlus
Моноидальные категории
Симметричные моноидальные категории
- Не позволяют переносить функции
- Не позволяют переиспользовать значения
- Не позволяют выбрасывать значения
- Единица может быть получена и выброжена
volga.syntax.symon
Симметричные моноидальные категории
- Чистота
- Тотальность
- Линейная семантика
- Tagless Final Friendly
trait Symon[->[_, _], x[_, _], I]{
def id[A]: A -> A
def compose[A, B, C](f: B -> C, g: A -> B): A -> C
def tensor[A, B, C, D](f: A -> C, g: B -> D): (A x B) -> (C x D)
def assocl[A, B, C]: (A x (B x C)) -> ((A x B) x C)
def swap[A, B]: (A x B) -> (B x A)
def lunit[A]: (I x A) -> A
def unitl[A]: A -> (I x A)
}
Симметричные моноидальные категории
Симметричные моноидальные категории
- Реактивные потоки
- Протоколы распределённых приложений
- serverless
- Tensorflow, вычислительные графы, символьно дифференцируемые функции
- Языки запросов СУБД
- Бизнес-правила
- (ко) Эффекты
Будущее
- 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
symple
By Oleg Nizhnik
symple
- 1,422