Monad presentation scala as a category

Preview:

DESCRIPTION

 

Citation preview

Category Theory

Theory and Applications for Functional Programming

Format of Talk1. Introduce definition from Category Theory2. Use something from Scala as an example3. Show it satisfies the definition.4. Do ^^ until Monad defined5. Prove that definition given in FP is equivalent to definition given in Category TheoryPlus 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 - Category1. a class ob(C) of objects2. 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 SLet 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, nowFor 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) - associativityFor 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 TypesMany 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 preservationAnd it’s obvious that someList.map(f).map(g) = someList.map(g o f), so satisfies composition preservation

Practical PointWhen 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 - FlattenLet F: S -> S and G: S -> S be the Option[Option[ _ ]] Functor and Option[ _ ] Functor respectively.NOTATION: will be sloppy henceforthLet 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 - ContinuedLet 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)).flattenand G(f) o N_X = .flatten.map(f). NowSome(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 PointKnowing an operation is a natural transformation makes refactoring easier.

Definition - Monad!!

Example - Option MonadLet 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]]soM_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 equalSome(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.flattenTherefore we have the first coherence condition ...

Example - Option Monad continuedNow 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])soF(N_X) = .map(Some), soM_X o F(N_X) = .map(Some).flatten, which is clearly the identity Functor (other way round - exercise)

Definition - Monad in FPIn 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], wherex.flatMap(N) = x

Theorem - EquivalenceThe 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 continuedNow 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 ReadingMonoids - 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 Applications1. Using Category Theoretic notions in code is a little like a formalization of design patterns2. When a reader sees a particular notion, they need to use less cognitive resources to comprehend the code by familiarity3. 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 shortcuts4. 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 Theory6. State hiding FP design

Recommended