Composition of
Arrows
and
Symmetric Monoidal Categories
in Scala

Oleg Nizhnik
Monads
trait Monad[F[_]] {
  def pure[A](a: A): F[A]
  def flatMap[A, B](a: A)(f: A => F[B]): F[B]
}Composition uses functions as continuations
- Have access to a wide language capabilities
- opaque
case study: Console
- Implement Console process. Should be able to
	- read line
- write ine
 
- echo2 : read two lines from the input and print concatenated string to the output
- countGets calculate how many lines would be read
Attempt 1: Monads
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 putLine(s: String): ConsoleM[Unit] = PutLine(s)
}  def echo2: ConsoleM[Unit] =
    for {
      x <- getLine
      y <- getLine
      _ <- putLine(x + y)
    } 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)
  }Attempt 1: Monads
FAILED
Attempt 2: Applicatives
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)
    }
  val getLine: ConsoleA[String]          = GetLine
  def putLine(s: String): ConsoleA[Unit] = PutLine(s)
}Attempt 2: Applicatives
  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] = {
    val start = (getLine, getLine).tupled
    putLine(???)
  }FAILED
Arrows
trait Arr[->[_, _]] {
  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
}Attempt 3: Arrows
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]
Attempt 3: Arrows
  implicit val arrow: Arr[ConsoleArr] = new Arr[ConsoleArr] {
    def lift[A, B](f: A => B): ConsoleArr[A, B] = Lift(f)
    def split[A, B, C, D](
    	fab: ConsoleArr[A, B], 
        fcd: ConsoleArr[C, D]
        ): ConsoleArr[(A, C), (B, D)] = 
      Split(fab, fcd)
    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
    
  val echo2Verbose: ConsoleArr[Unit, Unit] =
    liftf((_: Unit) => ((), ())) andThen 
      (getLine split getLine) andThen concat andThen 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
  }Attempt 3: Arrows
SUCCESS
Arrow Use Cases
- Explicit sequential \ parallel computation
- Performance https://github.com/traneio/arrows
- Parsing
- Introspection
- Optimizations
- Strictness
Monoid
trait Monoid[A] {
  def neutral: A
  def combine(x: A, y: A): A
}
Category
trait Cat[->[_, _]] {
  def id[A]: A -> A
  def compose[A, B, C](f: B -> C, g: A -> B): A -> C
}

Isomorphism
  case class <->[A, B](to: A -> B, from: B -> A) {
    def section    = compose(to, from) === id
    def retraction = compose(from, to) === id
  }
Monoidal category

Symmetric Monoidal Category

Tensor product is bifunctor

Pentagonal identity
Triagonal identity
Unit coherence
Associativity coherence
Coherence law

Symmetric Monoidal Category
  trait Symon[->[_, _], x[_, _], I]{
    def id[A]: A -> A
    
    def compose[A, B, C](f: B -> C, g: A -> B): A -> C
    
    def split[A, B, C, D](f: A -> B, g: C -> D): (A x C) -> (B x D)
    
    def lunit[A]: (I x A) -> A
    
    def unitl[A]: A -> (I x A) 
    
    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)
  }
Symmetric Monoidal Category
Closed Monoidal Category
Cartesian Category
Cartesian Closed Category
Simply Typed Lambda Calculus
Curry-Howard
Correspondence
Logic
Type System
Category
-Lambek
Example
  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) >>> assocr >>> (id[String] x plus) >>> user
Light Linear Lambda Calculus
Curry-Howard
Correspondence
Linear Logic
Linear Types
Monoidal Category
-Lambek
Volga
is comprehension syntax
for arrows
and symmetric monoidal categories
def echo2: ConsoleArr[Unit, Unit] =
    (getLine &&& getLine) >>> concat >>> putLine
def echo2s: ConsoleArr[Unit, Unit] = arr { () =>
  val s1 = getLine()
  val s2 = getLine()
  val s = concat(s1, s2)
  putLine(s)
}Volga
trait Parsing[I, O] {
  def parse(input: I): EitherNel[String, O]
  def print(o: O): I
}Parsing
invertible parsing accumulating errors
def sep(sep: String): Parsing[String, (String, String)]
val readInt: Parsing[String, Int]
val date: Parsing[((Int, Int), Int), LocalDate] 
import volga.syntax.comp._
import volga.syntax.cat._
import volga.syntax.symmon._
implicit val parsingSMC: Symon[Parsing, (*, *), Unit]
val parsing = symon[Parsing, (*, *), Unit]Parsing
  val parseDate: Parsing[String, LocalDate] = parsing { (s: V[String]) =>
    val (dayStr, monthYear) = sep(".")(s)
    val (monthStr, yearStr) = sep(".")(monthYear)
    ----
    val day   = readInt(dayStr)
    val month = readInt(monthStr)
    val year  = readInt(yearStr)
    date(day, month, year)
  }
Volga
val parseDate1: Parsing[String, LocalDate] = sep(".")
    .andThen(ident[Parsing, String].split(sep(".")))
    .andThen(parsingSMC.assocl[String, String, String])
    .andThen(readInt.split(readInt).split(readInt))
    .andThen(date)Volga
  val parseDate1: Parsing[String, LocalDate] = parsing { (s: V[String]) =>
    val (dayStr, monthYear) = sep(".")(s)
    val (monthStr, yearStr) = sep(".")(monthYear)
    ----
    val year  = readInt(yearStr)
    val month = readInt(monthStr)
    val day   = readInt(dayStr)
    date(day, month, year)
  }Volga
Change the order
  val parseDate: Parsing[String, LocalDate] = sep(".")
    .andThen(ident[Parsing, String].split(sep(".")))
    .andThen(
      parsingSMC
        .assocl[String, String, String]
        .andThen(parsingSMC.swap[String, String].split(ident[Parsing, String]))
        .andThen(parsingSMC.assocr[String, String, String])
        .andThen(ident[Parsing, String].split(parsingSMC.swap[String, String]))
        .andThen(parsingSMC.assocl[String, String, String])
        .andThen(parsingSMC.swap[String, String].split(ident[Parsing, String]))
    )
    .andThen(readInt.split(readInt).split(readInt))
    .andThen(
      parsingSMC
        .assocr[Int, Int, Int]
        .andThen(parsingSMC.assocl[Int, Int, Int])
        .andThen(parsingSMC.swap[Int, Int].split(ident[Parsing, Int]))
        .andThen(parsingSMC.assocr[Int, Int, Int])
        .andThen(ident[Parsing, Int].split(parsingSMC.swap[Int, Int]))
        .andThen(parsingSMC.assocl[Int, Int, Int])
        .andThen(parsingSMC.swap[Int, Int].split(ident[Parsing, Int]))
    )
    .andThen(date)Volga
...leads to a lot more operations
  val parseDateTime: Parsing[String, LocalDateTime] = parsing { (s: V[String]) =>
    val (dayStr, rest1)        = sep(".")(s)
    val (monthStr, rest2)      = sep(".")(rest1)
    val (yearStr, rest3)       = sep(" ")(rest2)
    val (hourStr, rest4)       = sep(":")(rest3)
    val (minuteStr, secondStr) = sep(":")(rest4)
    ----
    val year   = readInt(yearStr)
    val month  = readInt(monthStr)
    val day    = readInt(dayStr)
    val hour   = readInt(hourStr)
    val minute = readInt(minuteStr)
    val second = readInt(secondStr)
    ----
    val d = date(day, month, year)
    val t = time(hour, minute, second)
    dateTime(d, t)
  }Volga
complex logic
  val parseDateAndTimeManual =
    sep(".")
      .andThen(ident[Parsing, String].split(sep(".")))
      .andThen(
        parsingSMC.assocl[String, String, String].andThen(parsingSMC.swap[String, String].split(ident[Parsing, String]))
      )
      .andThen(ident[Parsing, (String, String)].split(sep(" ")))
      .andThen(
        ident[Parsing, (String, String)]
          .split(ident[Parsing, (String, String)])
          .andThen(parsingSMC.assocr[String, String, (String, String)])
          .andThen(
            ident[Parsing, String]
              .split(
                parsingSMC
                  .assocl[String, String, String]
                  .andThen(
                    parsingSMC
                      .swap[String, String]
                      .split(ident[Parsing, String])
                  )
                  .andThen(parsingSMC.assocr[String, String, String])
              )
          )
          .andThen(parsingSMC.assocl[String, String, (String, String)])
          .andThen(parsingSMC.swap[String, String].split(ident[Parsing, (String, String)]))
          .andThen(parsingSMC.assocl[(String, String), String, String])
      )
      .andThen(ident[Parsing, Tuple2[Tuple2[String, String], String]].split(sep(":")))
      .andThen(
        parsingSMC
          .assocr[(String, String), String, (String, String)]
          .andThen(parsingSMC.assocr[String, String, (String, (String, String))])
          .andThen(
            ident[Parsing, String]
              .split(
                parsingSMC
                  .assocl[String, String, (String, String)]
                  .andThen(ident[Parsing, (String, String)].split(ident[Parsing, (String, String)]))
                  .andThen(parsingSMC.assocr[String, String, (String, String)])
                  .andThen(
                    ident[Parsing, String]
                      .split(
                        parsingSMC
                          .assocl[String, String, String]
                          .andThen(
                            parsingSMC
                              .swap[String, String]
                              .split(ident[Parsing, String])
                          )
                          .andThen(parsingSMC.assocr[String, String, String])
                      )
                  )
                  .andThen(parsingSMC.assocl[String, String, (String, String)])
                  .andThen(parsingSMC.swap[String, String].split(ident[Parsing, (String, String)]))
                  .andThen(parsingSMC.assocr[String, String, (String, String)])
              )
          )
          .andThen(parsingSMC.assocl[String, String, (String, (String, String))])
          .andThen(parsingSMC.assocl[(String, String), String, (String, String)])
          .andThen(parsingSMC.assocl[((String, String), String), String, String])
      )
      .andThen(ident[Parsing, (((String, String), String), String)].split(sep(":")))
      .andThen(
        parsingSMC
          .assocr[((String, String), String), String, (String, String)]
          .andThen(parsingSMC.assocr[(String, String), String, (String, (String, String))])
          .andThen(parsingSMC.assocr[String, String, (String, (String, (String, String)))])
          .andThen(
            ident[Parsing, String]
              .split(
                parsingSMC
                  .assocl[String, String, (String, (String, String))]
                  .andThen(
                    parsingSMC
                      .swap[String, String]
                      .split(ident[Parsing, (String, (String, String))])
                  )
                  .andThen(parsingSMC.assocr[String, String, (String, (String, String))])
                  .andThen(
                    ident[Parsing, String]
                      .split(
                        parsingSMC
                          .assocl[String, String, (String, String)]
                          .andThen(
                            parsingSMC
                              .swap[String, String]
                              .split(ident[Parsing, (String, String)])
                          )
                          .andThen(parsingSMC.assocr[String, String, (String, String)])
                      )
                  )
                  .andThen(parsingSMC.assocl[String, String, (String, (String, String))])
                  .andThen(ident[Parsing, (String, String)].split(ident[Parsing, (String, (String, String))]))
                  .andThen(parsingSMC.assocr[String, String, (String, (String, String))])
              )
          )
          .andThen(parsingSMC.assocl[String, String, (String, (String, (String, String)))])
          .andThen(parsingSMC.assocl[(String, String), String, (String, (String, String))])
          .andThen(parsingSMC.assocl[((String, String), String), String, (String, String)])
          .andThen(parsingSMC.assocl[(((String, String), String), String), String, String])
      )
      .andThen(
        readInt
          .split(readInt)
          .split(readInt)
          .split(readInt)
          .split(readInt)
          .split(readInt)
      )
      .andThen(
        parsingSMC
          .assocr[(((Int, Int), Int), Int), Int, Int]
          .andThen(parsingSMC.assocr[((Int, Int), Int), Int, (Int, Int)])
          .andThen(parsingSMC.assocr[(Int, Int), Int, (Int, (Int, Int))])
          .andThen(parsingSMC.swap[Int, Int].split(ident[Parsing, (Int, (Int, (Int, Int)))]))
          .andThen(parsingSMC.assocr[Int, Int, (Int, (Int, (Int, Int)))])
          .andThen(
            ident[Parsing, Int]
              .split(
                parsingSMC
                  .assocl[Int, Int, (Int, (Int, Int))]
                  .andThen(parsingSMC.swap[Int, Int].split(ident[Parsing, (Int, (Int, Int))]))
                  .andThen(parsingSMC.assocr[Int, Int, (Int, (Int, Int))])
              )
          )
          .andThen(parsingSMC.assocl[Int, Int, (Int, (Int, (Int, Int)))])
          .andThen(parsingSMC.swap[Int, Int].split(ident[Parsing, (Int, (Int, (Int, Int)))]))
          .andThen(parsingSMC.assocr[Int, Int, (Int, (Int, (Int, Int)))])
          .andThen(
            ident[Parsing, Int]
              .split(
                ident[Parsing, Int]
                  .split(
                    ident[Parsing, Int]
                      .split(parsingSMC.assocl[Int, Int, Int])
                  )
              )
          )
          .andThen(parsingSMC.assocl[Int, Int, (Int, ((Int, Int), Int))])
          .andThen(parsingSMC.assocl[(Int, Int), Int, ((Int, Int), Int)])
      )
      .andThen(date.split(time))
      .andThen(dateTime)Volga
...undoable by hand
146 operations
  val parseDate1: Parsing[String, LocalDate] = parsing { (s: V[String]) =>
    val (dayStr, monthYear) = SMCSyn(sep(".")).apply(s)
    val (monthStr, yearStr) = SMCSyn(sep(".")).apply(monthYear)
    ----
    val year  = SMCSyn(readInt).apply(yearStr)
    val month = SMCSyn(readInt).apply(monthStr)
    val day   = SMCSyn(readInt).apply(dayStr)
    SMCSyn(date).apply(day, month, year)
  }Volga
Syntactic extensions
  val parseDate1: Parsing[String, LocalDate] = parsing { (s: V[String]) =>
    val (dayStr: V[String], monthYear: V[String]) = SMCSyn(sep(".")).apply(s)
    val (monthStr: V[String], yearStr: V[String]) = SMCSyn(sep(".")).apply(monthYear)
    ----
    val year: V[Int] = SMCSyn(readInt).apply(yearStr)
    val month: V[Int] = SMCSyn(readInt).apply(monthStr)
    val day: V[Int] = SMCSyn(readInt).apply(dayStr)
    SMCSyn(date).apply(day, month, year)
  }
Volga
Type inference.
Available in your favorite IDE today
Related works

Related works

- Purity
- Totality
- Linear-like type system
- Tagless Final Friendly
Symmetric Monoidal Category
- Do not require function lifting
- Exactly one use , except unit type
- Reactive streams
- Distributed, session types, code mobility
- Serverless
- Linear algebra, computation graphs, auto-differentiation
- Database languages
- Business rules
- (Co)effects
Symmetric Monoidal Category
use cases
  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)
  }Symmetric Monoidal Category
Questions
email: odomontois@gmail.com, o.nizhnikov@tinkoff.ru
telegram: @odomontois
Composition of arrows and SMC
By Oleg Nizhnik
Composition of arrows and SMC
- 1,112
 
   
  