Category Theory for
beginnersYOW Lambda Jam 2015
@KenScambler
A B
C
Functions
They must be good; we even have conferences for them
A B
Set
f
functionSet
A B
elements of A
elements of B
f
Mappings from
individual A elements
to B elements
We like functions.
They’re simple.
They compose.
What if we keep
the composition,
and throw away
sets & functions?
A
Something
f
Some composable relation
between A & B Something
B
It turns out that you
can describe most of
maths, just from this
starting point!
Dizzyingly abstract
Categories appear at every
step of the abstraction
continuum
For pragmatic real-world
programmers with deadlines?
Couple of things.
Couple of things.
Programming = maths
Couple of things.
Programming = maths
Programming = abstraction
Perhaps categories
have something to
offer us after all!
Category
Objects1.
Category
Objects
Arrows or morphisms2.
Category
Objects
Arrows
Domain f
dom(f)
3.
Category
Objects
Arrows
Domain/Codomain f
cod(f)
dom(f)
4.
Category
Objects
Arrows
Domain/Codomain
dom(g)
cod(g)
g4.
Category
Objects
Arrows
Domain/Codomain4.
Category
Objects
Arrows
Domain/Codomain
Composition
f
5.
Category
Objects
Arrows
Domain/Codomain
Composition
fg
5.
Category
Objects
Arrows
Domain/Codomain
Composition
fg
g ∘ f
5.
Category
Objects
Arrows
Domain/Codomain
Composition
f
5.
Category
Objects
Arrows
Domain/Codomain
Composition
f
h5.
Category
Objects
Arrows
Domain/Codomain
Composition
f
h
h ∘ f
5.
Category
Objects
Arrows
Domain/Codomain
Composition
Identity6.
Category
Compose
∘ : (B C, A B) => (A C)
Identity
id : A A
Category Laws
Associative Law
(f ∘ g) ∘ h = f ∘ (g ∘ h )
Identity Laws
f ∘ id = id ∘ f = f
Associative law
(f ∘ g) ∘ h = f ∘ (g ∘ h )
f
g
h
(g ∘ h)
(f ∘ g)
Associative law
(f ∘ g) ∘ h = f ∘ (g ∘ h )
f
g
h
(g ∘ h)
(f ∘ g)
Associative law
(f ∘ g) ∘ h = f ∘ (g ∘ h )
f
g
h
(g ∘ h)
(f ∘ g)
Associative law
(f ∘ g) ∘ h = f ∘ (g ∘ h )
f
g
h
(g ∘ h)
(f ∘ g)
Associative law
(f ∘ g) ∘ h = f ∘ (g ∘ h )
f
g
h
(g ∘ h)
(f ∘ g)
Associative law
(f ∘ g) ∘ h = f ∘ (g ∘ h )
f
g
h
(g ∘ h)
(f ∘ g)
Associative law
(f ∘ g) ∘ h = f ∘ (g ∘ h )
f
g
h
(g ∘ h)
(f ∘ g)
Identity laws
f ∘ id = id ∘ f = f
fid id
Identity laws
f ∘ id = id ∘ f = f
fid id
Identity laws
f ∘ id = id ∘ f = f
fid id
Identity laws
f ∘ id = id ∘ f = f
fid id
Sets & functions
Person
String Integer
bestFriend
length
name age
+1
(not identity!)
(not identity!)
Sets & functions
Infinite arrows from composition
+1∘ length ∘ name
bestFriend ∘ bestFriend
bestFriend ∘ bestFriend ∘ bestFriend
+1∘ age∘ bestFriend
Sets & functions
Objects
Arrows
Composition
Identity
Sets & functions
Objects = sets (or types)
Arrows = functions
Composition = function composition
Identity = identity function
Categories in code
trait Category[Arr[_,_]] {
def compose[A,B,C](c: Arr[B,C], d: Arr[A,B]): Arr[A,C]
def id[A]: Arr[A,A]
}
Category of Types & Functions
object FnCatextends Category[Function1] {
def compose[A,B,C](c: B => C, d: A => B): A => C = {
a => c(d(a)) }
def id[A]: A => A = (a => a)}
Category of Garden Hoses
sealed trait Hose[In, Out] {def leaks: Intdef kinked: Boolean
def >>[A](in: Hose[A, In]):Hose[A, Out]
def <<[A](out: Hose[Out, A]):Hose[In, A]
}
Category of Garden Hoses
object HoseCatextends Category[Hose] {
def compose[A,B,C](c: Hose[B,C], d: Hose[A,B]): Hose[A,C] = {
c << d }
def id[A]: Hose[A,A] = EmptyHose
}
Categories embody the
principle of
strongly-typed
composability
Functors
Functors map between categories
Objects objects
Arrows arrows
Preserves composition & identity
CF
DCategory Category
Functor
CF
DCategory Category
Functor
CatCategory of categories
CF
DCategory Category
Functor
CatCategory of categories
Objects = categories
Arrows = functors
Composition = functor composition
Identity = Identity functor
Functors in code
trait Functor[F[_]] {
def map[A,B](fa: F[A],
f: A => B): F[B]
}
Functors in code
trait Functor[F[_]] {
def map[A,B](fa: F[A],
f: A => B): F[B]
}
Objects to objects
Functors in code
trait Functor[F[_]] {
def map[A,B](fa: F[A],
f: A => B): F[B]
}
Arrows to arrows
Functors in code
class Functor f where
fmap :: (a b) (f a f b)
Arrows to arrows
Composable systems
Growing a system
Banana
Growing a system
Bunch
Growing a system
BunchBunch
Growing a system
BunchBunch
Bunch
Growing a system
BunchBunch
Bunch
BunchManager
Growing a system
BunchBunch
Bunch
BunchManager
AnyManagers
Using composable abstractions
means your code can grow
without getting more complex
Categories capture the essence
of composition in software!
Look for Categories (such
as Monoids) in your
domain where you can
You can go out of your way to
bludgeon non-composable
things into a category
Spanner
AbstractSpanner
Spanner
AbstractSpanner
AbstractToolThing
Spanner
AbstractSpanner
AbstractToolThing
GenerallyUsefulThing
Spanner
AbstractSpanner
AbstractToolThing
GenerallyUsefulThing
AbstractGenerallyUsefulThingFactory
Spanner
AbstractSpanner
AbstractToolThing
GenerallyUsefulThing
AbstractGenerallyUsefulThingFactory
WhateverFactoryBuilder
That’s not what
abstraction means.
Code shouldn’t know
things that aren’t needed.
def getNames(users: List[User]): List[Name] = {
users.map(_.name)}
def getNames(users: List[User]): List[Name] = {
println(users.length) users.map(_.name)
}
Over time…
def getNames(users: List[User]): List[Name] = {
println(users.length) if (users.length == 1) {
s”${users.head.name} the one and only"
} else { users.map(_.name)
}}
“Oh, now we need
the roster of names!
A simple list won’t
do.”
def getRosterNames(users: Roster[User]): Roster[Name] = {
users.map(_.name)}
def getRosterNames(users: Roster[User]): Roster[Name] = {
LogFactory.getLogger.info(s”When you party with ${users.rosterTitle}, you must party hard!")
users.map(_.name)}
Over time…
def getRosterNames(users: Roster[User]): Roster[Name] = {
LogFactory.getLogger.info(s"When you party with ${users.rosterTitle}, you must party hard!")
if (users.isFull) EmptyRoster("(everyone)")
else users.map(_.name)}
When code knows too much,
soon new things will appear that
actually require the other stuff.
Coupling has increased.
The mixed concerns will
tangle and snarl.
Code is rewritten each time for
trivially different requirements
def getNames[F: Functor](users: F[User]): F[Name] = {
Functor[F].map(users)(_.name)}
getNames(List(alice, bob, carol))
getNames(Roster(alice, bob, carol))
Not only is the abstract code
not weighed down with
useless junk, it can’t be!
Reusable out of the box!
Abstraction is
about hiding
unnecessary
information. This
a good thing.
We actually know more about what the code
does, because we have stronger guarantees!!
Categories show deep
underlying patterns
beneath superficially
different things
Set Set
function
Cat Cat
functor
Just about everything
ends up being in a
category, or being one.
There is no better
way to understand
the patterns
underlying software
than studying
Category Theory.