Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
Category Theory
Theory and Applications for Functional
Programming
Format of Talk
1. Introduce definition from Category Theory
2. Use something from Scala as an example
3. Show it satisfies the definition.
4. Do ^^ until Monad defined
5. Prove that definition given in FP is equivalent to
definition given in Category Theory
Plus random points about application and practicalities
along the way
Note about foundations
● Category Theory is itself a foundation of mathematics,
and strictly speaking you don’t need Set Theory to do
Category Theory
● Nevertheless a lot of examples and language used to
explain Category Theory is actually borrowed from Set
Theory
● I will do the same as it makes things much easier
(strictly speaking I’m using the von Neumann–Bernays–Gödel (NBG) set theory Axiomatization, which
is a conservative extension of ZFC that allows us to talk about Proper Classes (e.g. Class of all Sets)
Definition - Category
1. a class ob(C) of objects
2. a class hom(C) of morphisms, or arrows, or maps, between the objects. Each morphism f has a unique
source object a and target object b where a and b are in ob(C). We write f: a → b, and we say "f is a morphism
from a to b".
3. for every three objects a, b and c, a binary operation hom(a, b) × hom(b, c) → hom(a, c) called composition
of morphisms; the composition of f : a → b and g : b → c is written as g ∘ f or gf. (Some authors use
"diagrammatic order", writing f;g or fg.)
such that the following axioms hold:
a. (associativity) if f : a → b, g : b → c and h : c → d then h ∘ (g ∘ f) = (h ∘ g) ∘ f, and
b. (identity) for every object x, there exists a morphism 1x
: x → x (some authors write idx
) called the identity morphism for x, such
that for every morphism f : a → b, we have 1b
∘ f = f = f ∘ 1a
.
Example - Scala S
Let the set of Types in Scala be Ob(C)
Let the set of 1 param Functions in Scala be hom(C)
NOTATION: Methods of a Type A, that return a type B that take no params can
be equivalently considered as 1 param functions f: A -> B. Therefore I will
interchange method invocation and function application henceforth.
Composition o is defined as simply normal function composition, now
For any f, g, h (types obv) we have
(h o (g o f))(x) = (g o f)(h(x)) = f(g(h(x))) = f((h o g)(x)) = ((h o g) o f)(x) -
associativity
For any T, id_T : T -> T is defined by for any x is a T, id_T(x) = x, clearly this is
an identity
Definition - Functor
Example - Parameterized Types
Many parameterized types in Scala can be viewed as Functors with their map operation;
Let S be the Scala Category, and F: S -> S
associate any T in Ob(S) to List[T] in Ob(S)
associate any f: A -> B (for any A, B in Ob(S)) to map(f): List[A] -> List[B]. Now for any T in Ob(S)
F(id_T)(someList) = someList.map(x => x) = (x => x)(someList) = id_List[T] = id_F[T]
- so satisfies identity preservation
And it’s obvious that someList.map(f).map(g) = someList.map(g o f), so satisfies composition
preservation
Practical Point
When you write a parameterized type in an API in Scala with a map function, you are telling the API
user that map(f).map(g) is the same as map(f o g). So in Scalding, it’s often convenient to chain map
operations together for readability, rather than compose the functions - but Scalding is clever, it will
compose the functions for you so that your still O(N) not O(2N), O(3N) etc.
Definition - Natural Transformation
.If F and G are functors between the categories C and D, then a natural transformation η from F to G associates to every object X in C a
morphism ηX
: F(X) → G(X) between objects of D, called the component of η at X, such that for every morphism f : X → Y in C we have:
Example - Flatten
Let F: S -> S and G: S -> S be the Option[Option[ _ ]] Functor and Option[ _ ] Functor respectively.
NOTATION: will be sloppy henceforth
Let f: X -> Y in Hom(S)
So F(f): Option[Option[ X ]] -> Option[Option[ Y ] is .map(_.map(f))
G(f): Option[X] -> Option[Y] is .map(f)
Example - Flatten - Continued
Let N_x be .flatten[x], then flatten is a Natural Transformation:
N_Y = flatten[Y]: Option[Option[Y]] -> Option[Y]
N_X = flatten[X]: Option[Option[X]] -> Option[X]
So N_Y o F(f) = .map(_.map(f)).flatten
and G(f) o N_X = .flatten.map(f). Now
Some(Some(x)).map(_.map(f)).flatten = Some(Some(x).map(f)).flatten = Some(Some(f(x)).flatten
= Some(f(x)) = Some(x).map(f) = Some(Some(x)).flatten.map(f)
Note there are many more natural transformations, like if we defined toList on Option.
Practical Point
Knowing an operation is a natural transformation makes refactoring easier.
Definition - Monad!!
Example - Option Monad
Let F be the Option Functor, then combined with the flatten natural transformation M we have a
monad: For any X in Ob(S)
F(M_X) = map(_.flatten) : Option[Option[Option[X]]] -> Option[Option[X]]
M_F(X) = M_Option[X] = flatten[Option[X]]: Option[Option[Option[X]]] -> Option[Option[X]]
so
M_X o F(M_X) = .map(_.flatten).flatten : Option[Option[Option[X]]] -> Option[X]
M_X o M_F(X) = .flatten.flatten : Option[Option[Option[X]]] -> Option[X]
Let’s check these are equal
Some(Some(Some(x))).map(_.flatten).flatten = Some(Some(Some(x)).flatten).flatten
= Some(Some(x)).flatten = Some(x) = Some(Some(x)).flatten =
Some(Some(Some(x))).flatten.flatten
Therefore we have the first coherence condition ...
Example - Option Monad continued
Now our Identity natural transformation will be the Some function, i.e.
N_X = Some: X -> Option[X] (which is the same as Id(X) -> Option[X])
so
F(N_X) = .map(Some), so
M_X o F(N_X) = .map(Some).flatten, which is clearly the identity Functor (other way round - exercise)
Definition - Monad in FP
In functional programming a monadic Type M is simply defined in terms of
flatMap, where:
For any f: X -> M[Y], g: Y -> M[Z], and any x: M[X]
x.flatMap(f).flatMap(g) = x.flatMap(f(_).flatMap(g))
and there exists a neutral element N: X -> M[X], where
x.flatMap(N) = x
Theorem - Equivalence
The two previous definitions are equivalent when we make the following substitution
.map(f).flatten for flatMap(f) - (*)
Proof:
.flatten.flatten = .map(_.flatten).flatten - Monad Category Theory
=> .map(f(_).map(g)).flatten.flatten = .map(f(_).map(g)).map(_.flatten).flatten
- by substituting in .map(f(_).map(g))
Now RHS = .map(f(_).map(g).flatten).flatten - by Functor Composition Preservation
= .flatMap(f(_).map(g).flatten) - by (*)
= .flatMap(f(_).flatMap(g)) - by (*)
...
Proof continued
Now LHS = x.map(f(_)).map(_.map(g)).flatten.flatten - by Functor Composition Preservation
= x.map(f).flatten.map(g).flatten - since flatten is a natural transformation (recall earlier slide)
= x.flatMap(f).flatMap(g) - by (*) twice.
Therefore
.flatMap(f(_).flatMap(g)) = .flatMap(f).flatMap(g)
It remains to show the identity conditions (exercise)
Further Reading
Monoids - Used in Reduce operations in Map Reduce to parallelize operations
that cumulate a single value. E.g. + is a monoid.
Covariance and Contravariance - Used in Typing rules for type inference
Summary of Applications
1. Using Category Theoretic notions in code is a little like a formalization of
design patterns
2. When a reader sees a particular notion, they need to use less cognitive
resources to comprehend the code by familiarity
3. It’s easier to refactor code due to known equivalences, some of these
equivalences are even used by Intellij (and ReSharper for LINQ) for the auto-
refactor shortcuts
4. Sometimes APIs allow the user to write readable code, but the resulting
compiled code will be in it’s most computationally efficient representation.
5. Compilers use concepts in Category Theory
6. State hiding FP design

More Related Content

Monad presentation scala as a category

  • 1. Category Theory Theory and Applications for Functional Programming
  • 2. Format of Talk 1. Introduce definition from Category Theory 2. Use something from Scala as an example 3. Show it satisfies the definition. 4. Do ^^ until Monad defined 5. Prove that definition given in FP is equivalent to definition given in Category Theory Plus random points about application and practicalities along the way
  • 3. Note about foundations ● Category Theory is itself a foundation of mathematics, and strictly speaking you don’t need Set Theory to do Category Theory ● Nevertheless a lot of examples and language used to explain Category Theory is actually borrowed from Set Theory ● I will do the same as it makes things much easier (strictly speaking I’m using the von Neumann–Bernays–Gödel (NBG) set theory Axiomatization, which is a conservative extension of ZFC that allows us to talk about Proper Classes (e.g. Class of all Sets)
  • 4. Definition - Category 1. a class ob(C) of objects 2. a class hom(C) of morphisms, or arrows, or maps, between the objects. Each morphism f has a unique source object a and target object b where a and b are in ob(C). We write f: a → b, and we say "f is a morphism from a to b". 3. for every three objects a, b and c, a binary operation hom(a, b) × hom(b, c) → hom(a, c) called composition of morphisms; the composition of f : a → b and g : b → c is written as g ∘ f or gf. (Some authors use "diagrammatic order", writing f;g or fg.) such that the following axioms hold: a. (associativity) if f : a → b, g : b → c and h : c → d then h ∘ (g ∘ f) = (h ∘ g) ∘ f, and b. (identity) for every object x, there exists a morphism 1x : x → x (some authors write idx ) called the identity morphism for x, such that for every morphism f : a → b, we have 1b ∘ f = f = f ∘ 1a .
  • 5. Example - Scala S Let the set of Types in Scala be Ob(C) Let the set of 1 param Functions in Scala be hom(C) NOTATION: Methods of a Type A, that return a type B that take no params can be equivalently considered as 1 param functions f: A -> B. Therefore I will interchange method invocation and function application henceforth. Composition o is defined as simply normal function composition, now For any f, g, h (types obv) we have (h o (g o f))(x) = (g o f)(h(x)) = f(g(h(x))) = f((h o g)(x)) = ((h o g) o f)(x) - associativity For any T, id_T : T -> T is defined by for any x is a T, id_T(x) = x, clearly this is an identity
  • 7. Example - Parameterized Types Many parameterized types in Scala can be viewed as Functors with their map operation; Let S be the Scala Category, and F: S -> S associate any T in Ob(S) to List[T] in Ob(S) associate any f: A -> B (for any A, B in Ob(S)) to map(f): List[A] -> List[B]. Now for any T in Ob(S) F(id_T)(someList) = someList.map(x => x) = (x => x)(someList) = id_List[T] = id_F[T] - so satisfies identity preservation And it’s obvious that someList.map(f).map(g) = someList.map(g o f), so satisfies composition preservation Practical Point When you write a parameterized type in an API in Scala with a map function, you are telling the API user that map(f).map(g) is the same as map(f o g). So in Scalding, it’s often convenient to chain map operations together for readability, rather than compose the functions - but Scalding is clever, it will compose the functions for you so that your still O(N) not O(2N), O(3N) etc.
  • 8. Definition - Natural Transformation .If F and G are functors between the categories C and D, then a natural transformation η from F to G associates to every object X in C a morphism ηX : F(X) → G(X) between objects of D, called the component of η at X, such that for every morphism f : X → Y in C we have:
  • 9. Example - Flatten Let F: S -> S and G: S -> S be the Option[Option[ _ ]] Functor and Option[ _ ] Functor respectively. NOTATION: will be sloppy henceforth Let f: X -> Y in Hom(S) So F(f): Option[Option[ X ]] -> Option[Option[ Y ] is .map(_.map(f)) G(f): Option[X] -> Option[Y] is .map(f)
  • 10. Example - Flatten - Continued Let N_x be .flatten[x], then flatten is a Natural Transformation: N_Y = flatten[Y]: Option[Option[Y]] -> Option[Y] N_X = flatten[X]: Option[Option[X]] -> Option[X] So N_Y o F(f) = .map(_.map(f)).flatten and G(f) o N_X = .flatten.map(f). Now Some(Some(x)).map(_.map(f)).flatten = Some(Some(x).map(f)).flatten = Some(Some(f(x)).flatten = Some(f(x)) = Some(x).map(f) = Some(Some(x)).flatten.map(f) Note there are many more natural transformations, like if we defined toList on Option. Practical Point Knowing an operation is a natural transformation makes refactoring easier.
  • 12. Example - Option Monad Let F be the Option Functor, then combined with the flatten natural transformation M we have a monad: For any X in Ob(S) F(M_X) = map(_.flatten) : Option[Option[Option[X]]] -> Option[Option[X]] M_F(X) = M_Option[X] = flatten[Option[X]]: Option[Option[Option[X]]] -> Option[Option[X]] so M_X o F(M_X) = .map(_.flatten).flatten : Option[Option[Option[X]]] -> Option[X] M_X o M_F(X) = .flatten.flatten : Option[Option[Option[X]]] -> Option[X] Let’s check these are equal Some(Some(Some(x))).map(_.flatten).flatten = Some(Some(Some(x)).flatten).flatten = Some(Some(x)).flatten = Some(x) = Some(Some(x)).flatten = Some(Some(Some(x))).flatten.flatten Therefore we have the first coherence condition ...
  • 13. Example - Option Monad continued Now our Identity natural transformation will be the Some function, i.e. N_X = Some: X -> Option[X] (which is the same as Id(X) -> Option[X]) so F(N_X) = .map(Some), so M_X o F(N_X) = .map(Some).flatten, which is clearly the identity Functor (other way round - exercise)
  • 14. Definition - Monad in FP In functional programming a monadic Type M is simply defined in terms of flatMap, where: For any f: X -> M[Y], g: Y -> M[Z], and any x: M[X] x.flatMap(f).flatMap(g) = x.flatMap(f(_).flatMap(g)) and there exists a neutral element N: X -> M[X], where x.flatMap(N) = x
  • 15. Theorem - Equivalence The two previous definitions are equivalent when we make the following substitution .map(f).flatten for flatMap(f) - (*) Proof: .flatten.flatten = .map(_.flatten).flatten - Monad Category Theory => .map(f(_).map(g)).flatten.flatten = .map(f(_).map(g)).map(_.flatten).flatten - by substituting in .map(f(_).map(g)) Now RHS = .map(f(_).map(g).flatten).flatten - by Functor Composition Preservation = .flatMap(f(_).map(g).flatten) - by (*) = .flatMap(f(_).flatMap(g)) - by (*) ...
  • 16. Proof continued Now LHS = x.map(f(_)).map(_.map(g)).flatten.flatten - by Functor Composition Preservation = x.map(f).flatten.map(g).flatten - since flatten is a natural transformation (recall earlier slide) = x.flatMap(f).flatMap(g) - by (*) twice. Therefore .flatMap(f(_).flatMap(g)) = .flatMap(f).flatMap(g) It remains to show the identity conditions (exercise)
  • 17. Further Reading Monoids - Used in Reduce operations in Map Reduce to parallelize operations that cumulate a single value. E.g. + is a monoid. Covariance and Contravariance - Used in Typing rules for type inference
  • 18. Summary of Applications 1. Using Category Theoretic notions in code is a little like a formalization of design patterns 2. When a reader sees a particular notion, they need to use less cognitive resources to comprehend the code by familiarity 3. It’s easier to refactor code due to known equivalences, some of these equivalences are even used by Intellij (and ReSharper for LINQ) for the auto- refactor shortcuts 4. Sometimes APIs allow the user to write readable code, but the resulting compiled code will be in it’s most computationally efficient representation. 5. Compilers use concepts in Category Theory 6. State hiding FP design