Jean-Remi Desjardins - LambdaConf 2017
recursion schemes 💪
Meijer et. al go so far as to condemn functional programming without recursion schemes as morally equivalent to imperative programming with goto. While comparisons to Djikstra’s infamous letter to the ACM are often inane, the analogy is apt: just as using while and for loops rather than goto brings structure and harmony to imperative control flow, the use of recursion schemes over hand-written brings similar structure to recursive computations. This insight is so important that I’ll repeat it: recursion schemes are just as essential to idiomatic functional programming as for and while are to idiomatic imperative programming.
sumtypeofway.com
trait Expr
case class NumLit(value: Int) extends Expr
case class Add(left: Expr, right: Expr) extends Expr
case class Div(num: Expr, denum: Expr) extends Expr
trait Expr[A]
case class NumLit[A](value: Int) extends Expr[A]
case class Add[A](left: A, right: A) extends Expr[A]
case class Div[A](num: A, denum: A) extends Expr[A]
data Expr
= NumLit Int
| Add Expr Expr
| Div Expr Expr
data Expr a
= NumLit Int
| Add a a
| Div a a
Functorize your data types
trait Expr
case class NumLit(value: Int) extends Expr
case class Add(left: Expr, right: Expr) extends Expr
case class Div(num: Expr, denum: Expr) extends Expr
trait Expr
case class NumLit(value: Int) extends Expr
case class Add(left: Expr, right: Expr) extends Expr
case class Div(num: Expr, denum: Expr) extends Expr
val expr = Add(NumLit(5), NumLit(10))
// Increments all numeric literals in provided expression
def inc(expr: Expr): Expr =
expr match {
case Let(id, expr, in) => Let(id, inc(expr), inc(in))
case Add(left, right) => Add(inc(left), inc(right))
case NumLit(value) => NumLit(value + 1)
case other => other
}
inc(expr) // Add(NumLit(6), NumLit(11))
trait Expr[A]
case class NumLit[A](value: Int) extends Expr[A]
case class Add[A](left: A, right: A) extends Expr[A]
case class Div[A](num: A, denum: A) extends Expr[A]
val expr: Add[NumLit[Nothing]] = Add(NumLit(5), NumLit(10))
//val expr: Expr[Expr[Expr[...]]]
val exprFix: Fix[Expr] = Fix(Add(Fix(NumLit(5)), Fix(NumLit(10))))
trait Fix[F[_]] {
def cata[A](f: F[A] => A)(implicit func: Functor[F]): A
}
trait Expr[A]
case class NumLit[A](value: Int) extends Expr[A]
case class Add[A](left: A, right: A) extends Expr[A]
case class Div[A](num: A, denum: A) extends Expr[A]
val expr: Fix[Expr] = Fix(Add(Fix(NumLit(5)), Fix(NumLit(10))))
// Returns the "complexity" of the expression provided
def complexity(expr: Fix[Expr]): Int =
expr.cata {
case NumLit(value) => 1
case Add(left, right) => 1 + Math.max(left, right)
case Div(num, denum) => 1 + Math.max(num, denum)
}
object Expr {
implicit val functor: Functor[Expr] = ???
}
trait Expr[A]
case class NumLit[A](value: Int) extends Expr[A]
case class Add[A](left: A, right: A) extends Expr[A]
case class Div[A](num: A, denum: A) extends Expr[A]
val expr: Fix[Expr] = Fix(Add(Fix(NumLit(5)), Fix(NumLit(10))))
case class DivisionByZero(div: Div[Int])
def eval(expr: Fix[Expr]): DivisionByZero \/ Int = ???
trait Expr[A]
case class NumLit[A](value: Int) extends Expr[A]
case class Add[A](left: A, right: A) extends Expr[A]
case class Div[A](num: A, denum: A) extends Expr[A]
val expr: Fix[Expr] = Fix(Add(Fix(NumLit(5)), Fix(NumLit(10))))
def collect(a: Fix[Expr]): List[NumLit[_]] = ???
collect(expr)
trait Expr[A]
case class NumLit[A](value: Int) extends Expr[A]
case class Add[A](left: A, right: A) extends Expr[A]
case class Div[A](num: A, denum: A) extends Expr[A]
val expr: Fix[Expr] = Fix(Add(Fix(NumLit(5)), Fix(NumLit(10))))
def gen(complexity: Int): Fix[Expr]
trait Tree {
def children: List[Tree]
def transform(f: Tree => Tree): Tree
}
trait Expr extends Tree
case class Let(id: String, expr: Expr) extends Expr {
def children = List(expr)
}
case class NumLit(value: Int) extends Expr {
def children = Nil
}
case class Add(left: Expr, right: Expr) extends Expr {
def children = List(left, right)
}
What's the difference over this?
trait Expr[A]
case class NumLit[A](value: Int) extends Expr[A]
case class Add[A](left: A, right: A) extends Expr[A]
case class Div[A](num: A, denum: A) extends Expr[A]
case class Id[A](value: String) extends Expr[A]
case class Let[A](id: Id[Nothing], as: A, in: A) extends Expr[A]
def eval(expr: Fix[Expr]): DivisionByZero \/ Int = ???
Beyond cata (scope)
case class Fix[F[_]](unfix: ???) {
def cata[A](f: F[A] => A): A = ???
}
Roll your own
beyond cata #2 (different error message)
trait Expr[A]
case class NumLit[A](value: Int) extends Expr[A]
case class Add[A](left: A, right: A) extends Expr[A]
case class Div[A](num: A, denum: A) extends Expr[A]
val expr: Fix[Expr] = add(numLit(5), div(numLit(4), numLit(2)))
case class DivisionByZero(div: Div[Fix[Expr]])
def eval(expr: Fix[Expr]): DivisionByZero \/ Int = ???
eval(expr)
Cofree