- Ross Baker (@rossabaker)
- Chris McKinley (@fnowarn) – spoke on Thursday!
- Kris Nuttycombe (@nuttycom)
- Paul Snively (@paul_snively) – speaking at 3:00 in this room!
- andyscott/droste – Andy Scott (@andygscott)
- vil1/recursion-schemes-cookbook – Valentin Kasas (@ValentinKasas)
I’m also working on a recursion scheme cookbook with one of the organizers(?) (member of the program committee?) here – Valentin Kasas.
But this talk isn’t about recursion schemes. At least not directly. But in a different way, it is, because it’s about everything, because it’s about … Categories!
inThisBuild(Seq(
scalaOrganization := "org.typelevel",
scalaVersion := "2.12.4-bin-typelevel-4",
scalacOptions := Seq(
"-language:higherKinds",
"-Ykind-polymorphism"),
libraryDependencies := Seq(
"org.typelevel" %% "cats-core" % "1.3.1",
"org.typelevel" %% "cats-free" % "1.3.1")))
addCompilerPlugin(
"org.spire-math" %% "kind-projector" % "0.9.8")
import cats._
import cats.arrow._
import cats.data._
import cats.free._
import cats.implicits._
\begin{tikzcd} cata \end{tikzcd}
package shadow {
trait Recursive[T, F[_]] {
def cata[A](φ: F[A] => A): T => A
}
// toInt Zero == 0
// toInt Succ(Succ(Succ(Zero))) == 3
sealed trait Natural
final case object Zero extends Natural
final case class Succ(prev: Natural) extends Natural
object Natural {
implicit val recursive = new Recursive[Natural, Option] {
def cata[A](φ: Option[A] => A) = ???
}
val toInt = recursive.cata[Int] {
case None => 0
case Some(i) => i + 1
}
}
\begin{tikzcd} cata \end{tikzcd}
package shadow {
trait Recursive[T, F[_]] {
def cata[A](φ: F[A] => A): T => A
}
trait Corecursive[T, F[_]] {
def ana[A](φ: A => F[A]): A => T
}
\begin{tikzcd} cata \ar[rr] & & ana \end{tikzcd}
package shadow {
trait Recursive[T, F[_]] {
def histo[A]
(φ: F[Cofree[F, A]] => A)(implicit F: Functor[F])
: T => A
def cata[A]
(φ: F[ A ] => A): T => A
def para[A]
(φ: F[(T, A)] => A)(implicit F: Functor[F])
: T => A
}
\begin{tikzcd}
histo \ar[rr, crossing over] & & futu &
\
cata \ar[uu] \ar[dd] \ar[rr, crossing over] & & ana \ar[uu, crossing over] & \
\
para \ar[rr] & & apo \ar[from=uu, crossing over] &
\end{tikzcd}
package shadow {
trait Recursive[T, F[_]] {
def cata[ A]
(φ: F[A] => A ): T => A
def cataM[M[_]: Monad, A]
(φ: F[A] => M[A])(implicit F: Traverse[F])
: T => M[A]
}
\begin{tikzcd}
& histoM \ar[from=dd] \ar[rr] & & futuW
histo \ar[rr, crossing over] \ar[ur] & & futu \ar[ur] & \
& cataM \ar[dd] \ar[rr] & & anaW \ar[uu] \ar[dd] \
cata \ar[uu] \ar[dd] \ar[rr, crossing over] \ar[ur] & & ana \ar[uu, crossing over] \ar[ur] & \
& paraM \ar[rr] & & apoW \
para \ar[rr] \ar[ur] & & apo \ar[from=uu, crossing over] \ar[ur] &
\end{tikzcd}
package shadow {
sealed trait ≈>[F[_, _], G[_, _]] {
def apply[A, B](fab: F[A, B]): G[A, B]
}
trait Recursive[T, F[_]] {
def cata[A ]
(φ: F[A] => A): T => A
}
trait RecursiveK[T[_], F[_[_], _]] {
def cataK[A[_] ]
(φ: F[A, ?] ~> A): T ~> A
}
trait RecursiveB[T[_, _], F[_[_, _], _, _]] {
def cataB[A[_, _]]
(φ: F[A, ?, ?] ≈> A): T ≈> A
}
\begin{tikzcd}[cramped, column sep=small]
& histoMK \ar[from=dd] \ar[rr] & & futuWK & & histoM \ar[from=dd] \ar[rr] & & futuW & & histoMA \ar[from=dd] \ar[rr] & & futuWA
histoK \ar[rr, crossing over] \ar[ur] & & futuK \ar[ur] & & histo \ar[rr, crossing over] \ar[ur] & & futu \ar[ur] & & histoA \ar[rr, crossing over] \ar[ur] & & futuA \ar[ur] & \
& cataMK \ar[dd] \ar[rr] & & anaWK \ar[uu] \ar[dd] & & cataM \ar[dd] \ar[rr] & & anaW \ar[uu] \ar[dd] & & cataMA \ar[dd] \ar[rr] & & anaWA \ar[uu] \ar[dd] \
cataK \ar[uu] \ar[dd] \ar[rr, crossing over] \ar[ur] & & anaK \ar[uu, crossing over] \ar[ur] & & cata \ar[uu] \ar[dd] \ar[rr, crossing over] \ar[ur] & & ana \ar[uu, crossing over] \ar[ur] & & cataA \ar[uu] \ar[dd] \ar[rr, crossing over] \ar[ur] & & anaA \ar[uu, crossing over] \ar[ur] & \
& paraMK \ar[rr] & & apoWK & & paraM \ar[rr] & & apoW & & paraMA \ar[rr] & & apoWA \
paraK \ar[rr] \ar[ur] & & apoK \ar[from=uu, crossing over] \ar[ur] & & para \ar[rr] \ar[ur] & & apo \ar[from=uu, crossing over] \ar[ur] & & paraA \ar[rr] \ar[ur] & & apoA \ar[from=uu, crossing over] \ar[ur] &
\end{tikzcd}
<raises hand> ME!
Yeah, and this is my own curse, I get it. But what if we could avoid implementing it 24 times? Right? Do you think people writing Go ask this question? I mean, this is why we have type parameters, right? We’ve already solved this problem in a bunch of cases. This bugs us enough to fix.
Or have we hit the sweet spot, where we’ve abstracted just enough, but no more?
Besides the many already covered, there are at least two other dimensions that have come up in working with recursion schemes. We’re not going to cover these today, but I just wanted to point out that even with all of this complication, we’re still dealing with a simplification. Duplication looms large.package shadow {
trait Recursive[T, F[_]] {
def para[A](φ: F[(T, A)] => A)(implicit F: Functor[F])
: T => A
def epara[A](φ: (T, F[A]) => A)(implicit F: Functor[F])
: T => A
}
package shadow {
final case class EnvT[E, F[_], A](run: (E, F[A]))
trait Recursive[T, F[_]] {
def para[ A]
(φ: F[(T, A)] => A)
(implicit F: Functor[F])
: T => A
def paraT[W[_], A]
(φ: F[EnvT[T, W, A]] => A)
(implicit F: Functor[F], W: Comonad[W])
: T => A
}
\begin{tikzcd}[cramped, column sep=small]
& histoMK \ar[from=dd] \ar[rr] & & futuWK & & histoM \ar[from=dd] \ar[rr] & & futuW & & histoMA \ar[from=dd] \ar[rr] & & futuWA
histoK \ar[rr, crossing over] \ar[ur] & & futuK \ar[ur] & & histo \ar[rr, crossing over] \ar[ur] & & futu \ar[ur] & & histoA \ar[rr, crossing over] \ar[ur] & & futuA \ar[ur] & \
& cataMK \ar[dd] \ar[rr] & & anaWK \ar[uu] \ar[dd] & & cataM \ar[dd] \ar[rr] & & anaW \ar[uu] \ar[dd] & & cataMA \ar[dd] \ar[rr] & & anaWA \ar[uu] \ar[dd] \
cataK \ar[uu] \ar[dd] \ar[rr, crossing over] \ar[ur] & & anaK \ar[uu, crossing over] \ar[ur] & & cata \ar[uu] \ar[dd] \ar[rr, crossing over] \ar[ur] & & ana \ar[uu, crossing over] \ar[ur] & & cataA \ar[uu] \ar[dd] \ar[rr, crossing over] \ar[ur] & & anaA \ar[uu, crossing over] \ar[ur] & \
& paraMK \ar[rr] & & apoWK & & paraM \ar[rr] & & apoW & & paraMA \ar[rr] & & apoWA \
paraK \ar[rr] \ar[ur] & & apoK \ar[from=uu, crossing over] \ar[ur] & & para \ar[rr] \ar[ur] & & apo \ar[from=uu, crossing over] \ar[ur] & & paraA \ar[rr] \ar[ur] & & apoA \ar[from=uu, crossing over] \ar[ur] &
\end{tikzcd}
package shadow {
trait Recursive[T, F[_]] {
def histo[A]
(φ: F[Cofree[F, A]] => A)(implicit F: Functor[F])
: T => A
def cata[A]
(φ: F[ A ] => A)
: T => A
def para[A]
(φ: F[(T, A)] => A)(implicit F: Functor[F])
: T => A
}
package shadow {
trait Recursive[T, F[_]] {
type Compose[F[_], G[_], A] = F[G[A]]
def gcata[W[_]: Comonad, A]
(k: Compose[F, W, ?] ~> Compose[W, F, ?],
φ: F[W[A]] => A)
: T => A
def cata[A](φ: F[A] => A) =
gcata[Id, A](FunctionK.id, φ)
def histo[A]
(φ: F[Cofree[F, A]] => A)(implicit F: Functor[F]) =
gcata[Cofree[F, ?], A](distCofree, φ)
def para[A](φ: F[(T, A)] => A) =
gcata[(T, ?), A](distTuple, φ)
def distCofree
: Compose[F, Cofree[F, ?], ?] ~> Compose[Cofree[F, ?], F, ?] = ???
def distTuple: Compose[F, (T, ?), ?] ~> Compose[(T, ?), F, ?] = ???
}
\begin{tikzcd}[cramped, column sep=small]
& histoMK \ar[from=dd] \ar[rr] & & futuWK & & histoM \ar[from=dd] \ar[rr] & & futuW & & histoMA \ar[from=dd] \ar[rr] & & futuWA
histoK \ar[rr, crossing over] \ar[ur] & & futuK \ar[ur] & & histo \ar[rr, crossing over] \ar[ur] & & futu \ar[ur] & & histoA \ar[rr, crossing over] \ar[ur] & & futuA \ar[ur] & \
& cataMK \ar[dd] \ar[rr] & & anaWK \ar[uu] \ar[dd] & & cataM \ar[dd] \ar[rr] & & anaW \ar[uu] \ar[dd] & & cataMA \ar[dd] \ar[rr] & & anaWA \ar[uu] \ar[dd] \
cataK \ar[uu] \ar[dd] \ar[rr, crossing over] \ar[ur] & & anaK \ar[uu, crossing over] \ar[ur] & & cata \ar[uu] \ar[dd] \ar[rr, crossing over] \ar[ur] & & ana \ar[uu, crossing over] \ar[ur] & & cataA \ar[uu] \ar[dd] \ar[rr, crossing over] \ar[ur] & & anaA \ar[uu, crossing over] \ar[ur] & \
& paraMK \ar[rr] & & apoWK & & paraM \ar[rr] & & apoW & & paraMA \ar[rr] & & apoWA \
paraK \ar[rr] \ar[ur] & & apoK \ar[from=uu, crossing over] \ar[ur] & & para \ar[rr] \ar[ur] & & apo \ar[from=uu, crossing over] \ar[ur] & & paraA \ar[rr] \ar[ur] & & apoA \ar[from=uu, crossing over] \ar[ur] &
\end{tikzcd}
\begin{tikzcd}[cramped, column sep=small]
& cataMK \ar[rr] & & anaWK & & cataM \ar[rr] & & anaW & & cataMA \ar[rr] & & anaWA
cataK \ar[rr, crossing over] \ar[ur] & & anaK \ar[ur] & & cata \ar[rr, crossing over] \ar[ur] & & ana \ar[ur] & & cataA \ar[rr, crossing over] \ar[ur] & & anaA \ar[ur] & \
\end{tikzcd}
\begin{tikzcd}
& cataMK \ar[from=dd] \ar[rr] & & anaWK
cataK \ar[rr, crossing over] \ar[ur] & & anaK \ar[ur] & \
& cataM \ar[dd] \ar[rr] & & anaW \ar[uu] \ar[dd] \
cata \ar[uu] \ar[dd] \ar[rr, crossing over] \ar[ur] & & ana \ar[uu, crossing over] \ar[ur] & \
& cataMB \ar[rr] & & anaWB \
cataB \ar[rr] \ar[ur] & & anaB \ar[from=uu, crossing over] \ar[ur] &
\end{tikzcd}
package shadow {
trait Recursive[T, F[_]] {
def cata[A](φ: F[A] => A): T => A
def cataM[M[_]: Monad, A]
(φ: F[A] => M[A])(implicit F: Traverse[F])
: T => M[A]
}
package shadow {
trait Recursive[⟶[_, _], T, F[_]] {
def cata[A](φ: F[A] ⟶ A): T ⟶ A
}
object category {
type Recursiveʹ[T, F[_]] = Recursive[Function1, T, F]
type RecursiveM[M[_], T, F[_]] =
Recursive[Kleisli[M, ?, ?], T, F]
}
\begin{tikzcd}
& cataMK \ar[from=dd] \ar[rr] & & anaWK
cataK \ar[rr, crossing over] \ar[ur] & & anaK \ar[ur] & \
& cataM \ar[dd] \ar[rr] & & anaW \ar[uu] \ar[dd] \
cata \ar[uu] \ar[dd] \ar[rr, crossing over] \ar[ur] & & ana \ar[uu, crossing over] \ar[ur] & \
& cataMB \ar[rr] & & anaWB \
cataB \ar[rr] \ar[ur] & & anaB \ar[from=uu, crossing over] \ar[ur] &
\end{tikzcd}
\begin{tikzcd}
cataK \ar[rr] & & anaK & \
\
cata \ar[uu] \ar[dd] \ar[rr] & & ana \ar[uu] & \
\
cataB \ar[rr] & & anaB \ar[from=uu] &
\end{tikzcd}
Op
.
package shadow {
trait Recursive[T, F[_]] {
def cata[A](φ: F[A] => A): T => A
}
trait Corecursive[T, F[_]] {
def ana[A](φ: A => F[A]): A => T
}
type Op[⟶[_, _], A, B] = B ⟶ A
// A => B ⟷ Function1[A, B]
// B => A ⟷ Op[Function1, A, B]
package shadow {
trait Recursive[⟶[_, _], T, F[_]] {
def cata[A](φ: F[A] ⟶ A): T ⟶ A
}
object dual {
type Corecursive[⟶[_, _], T, F[_]] =
Recursive[Op[⟶, ?, ?], T, F]
}
\begin{tikzcd}
histo \ar[rr, crossing over] & & futu &
\
cata \ar[uu] \ar[dd] \ar[rr, crossing over] & & ana \ar[uu, crossing over] & \
\
para \ar[rr] & & apo \ar[from=uu, crossing over] &
\end{tikzcd}
\begin{tikzcd}
cataK & & & \
\
cata \ar[uu] \ar[dd] & & & \
\
cataB & & &
\end{tikzcd}
package shadow {
trait Recursive[⟶[_, _], T, F[_]] {
def cata[A ](φ: F[A] ⟶ A): T ⟶ A
}
trait RecursiveK[⟶[_[_], _[_]], T[_], F[_[_], _]] {
def cataK[A[_] ](φ: F[A, ?] ⟶ A): T ⟶ A
}
trait RecursiveB[⟶[_[_, _], _[_, _]], T[_, _], F[_[_, _], _, _]] {
def cataB[A[_, _]](φ: F[A, ?, ?] ⟶ A): T ⟶ A
}
package shadow {
trait Recursive[⟶[_, _], T, F[_]] {
def cata[A ](φ: F[A] ⟶ A): T ⟶ A
}
package shadow {
trait Recursive[⟶[_ <: AnyKind, _ <: AnyKind],
T <: AnyKind,
F[_ <: AnyKind] <: AnyKind] {
def cata[A <: AnyKind](φ: F[A] ⟶ A): T ⟶ A
}
\begin{tikzcd}
cataK & & & \
\
cata \ar[uu] \ar[dd] & & & \
\
cataB & & &
\end{tikzcd}
\begin{tikzcd} cata \end{tikzcd}
package shadow {
trait Recursive[⟶[_ <: AnyKind, _ <: AnyKind],
⟹[_ <: AnyKind, _ <: AnyKind],
P[_ <: AnyKind, _ <: AnyKind, _ <: AnyKind],
T <: AnyKind,
F[_ <: AnyKind] <: AnyKind] {
def gcata[W[_ <: AnyKind] <: AnyKind, A <: AnyKind]
(k: P[F, W, ?] ⟹ P[W, F, ?], φ: F[A] ⟶ A)
: T ⟶ A
}
- compiler support
- type inference
- library support
- compile-time cost
- cognitive load
}}}}}}}}}}}}}}}}}
- Formation (@formation_ai),
- Erik Osheim (@d6) for
kind-projector
, - Pascal Voitot (@mandubian) for
-Ykind-polymorphism
,
- Miles Sabin (@milessabin) for Typelevel Scala,
- Rob Norris (@tpolecat),
- Typelevel.org in general,
- Scale and Alexy Khrabrov (@ChiefScientist), and
- so many others inside and outside the Scala community.