There is a branch of mathematics — category theory — and it fits what we are discussing like nothing else, so we simply have to talk about it a little. The idea at its core is defiantly simple: forget what lies inside mathematical structures and look only at the connections between them. The theory works with just two kinds of entities. The first are objects: ; what they actually are, the category does not care. The second are morphisms, also known as arrows: each has a source and a target, .
Arrows can be glued together: for example, for and the composition is defined. Then come two axioms. First: composition is associative, . Second: every object has an identity arrow , neutral with respect to composition: . Objects, arrows and two axioms — that is what a category is.
In Haskell this pattern is captured in a type class — from the module:
class Category cat where
id :: cat a a
(.) :: cat b c -> cat a b -> cat a c
-- the class laws are exactly the category axioms:
f . id == f
id . f == f
(f . g) . h == f . (g . h)Here is the arrow itself, parameterised by source and target (kind ). Frankly, the most all-embracing instance of a category is : objects are Haskell types, morphisms are functions between them, composition is from the Prelude, the identity is :
instance Category (->) where
id x = x
(g . f) x = g (f x)
show :: Int -> String -- a morphism Int -> String
length :: String -> Int -- a morphism String -> Int
length . show :: Int -> Int -- their composition is a morphism tooThe real value of category theory for us is that every construction we discussed in this chapter can be stated in its language very cleanly and crisply:
- monoid — a category with a single object: the morphisms are the elements of the monoid, is the composition, is the identity;
- functor — a mapping from category to category: objects go to objects, arrows go to arrows, and identity and composition are preserved (these are exactly the laws); our functors map into itself, which is why they are called endofunctors — the prefix “endo-” is Greek for “within”: a functor from a category into that very category;
- applicative functor — a monoidal functor: besides arrows it also carries pairs across, glues into , and provides the unit;
- monad — the category of Kleisli arrows with composition and identity ;
- Foldable — a fold into a category with a single object: carries the elements of the structure into a monoid and glues them there;
- Traversable — swapping two endofunctors around: .
The point about the monad can be touched with your hands: the Kleisli category is a newtype wrapper with a instance:
newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }
instance Monad m => Category (Kleisli m) where
id = Kleisli pure
Kleisli g . Kleisli f = Kleisli (f >=> g)
-- the familiar half — now as a Kleisli arrow:
half :: Kleisli Maybe Int Int
half = Kleisli (\x -> if even x then Just (x `div` 2) else Nothing)
quarter :: Kleisli Maybe Int Int
quarter = half . half -- an ordinary dot glued effectful arrows together
runKleisli quarter 12 -- Just 3
runKleisli quarter 6 -- Nothing (3 is odd — the second halving failed)Checking the laws for this instance means checking the monad laws: the identity and the associativity of we have already seen in the section on Kleisli arrows. A monad and its Kleisli category can be recovered from each other: they are one construction written down in two ways.
We arrived at Haskell along the path of the λ-calculus: terms, reductions, types. But one could come from the other side as well: the typed λ-calculus and cartesian closed categories are one and the same subject in two languages. Then types are objects, programs are morphisms, and the constructions of this chapter are categorical definitions.