I have been programming Haskell for a while now, and keep running across
strange terms from type theory, or category theory, or lambda calculus or
whatever, ususally related to type-classes in the Haskell library. This
page attempts to keep a glossary with simple explanations and examples for
each type-class. I primarily use as my examples: Maybe, lists, and my
Point3 vector type, as these are all fairly straightforward.
- Applicative: While the fmap function of Functor (see Functor,
below) is useful, it cannot be used, for example, to zip two data structures
together. While Applicative is quite bizarre at first, it allows you to use
liftA2, which can be thought of as the Applicative/Functor version of
liftM2, or the Functor version of zipWith. From Applicative, I mainly use
liftA2 and the pure function (which creates an item, filling every slot with
the given value). Example uses:
pure 4 :: [Int] == [4]
pure 4 :: Maybe Int == Just 4
pure 4 :: Point3' Int == Point3 (4, 4, 4)
add :: Point3' Int -> Point3' Int -> Point3' Int
add = liftA2 (+)
boundingBox :: Ord a => [Point2' a] -> (Point2' a, Point2' a)
boundingBox = foldl1 (liftA2 min) &&& foldl1 (liftA2 max) -- Uses Arrow
Example instance:
instance Applicative Point3' where
pure a = Point3 (a, a, a)
(<*>) (Point3 (fa, fb, fc)) (Point3 (a, b, c)) = Point3 (fa a, fb b, fc c)
- Arrows: When you manipulate things inside a monad, there is no way
of keeping track of what you started with. If you have IO Int, that's all
you have. Arrows allow a bit extra, so that you can parameterise over a
type you began with. For example, the function arrow -> is an arrow.
They may be more general than monads, but I've yet to use one in a practical
circumstance.
What I do use are the &&& and *** operators (as well as the first
and second functions), which can be useful with the function arrow (allowing you to
manipulate pairs more easily). Here are the equivalent definitions in
lambdas for functions:
f &&& g == \a -> (f a, g a)
f *** g == \(a, b) -> (f a, g b)
first f == \(a, b) -> (f a, b)
second f == \(a, b) -> (a, f b)
- Foldable: A generalisation of foldr that can work with any wrapper
type. The function toList is supplied built on top of Foldable, and this
can be useful with various types (such as Seq from Data.Sequence), as well as
your own. There is also an implementation of mapM_ for all Foldable things.
Example uses:
toList (Point3 (3,4,5)) == [3,4,5]
pointsEqual :: Eq a => Point3' a -> Point3' a -> Bool
pointsEqual a b = foldr (&&) True $ liftA2 (==) a b -- Uses Applicative too
Example instance:
instance Foldable Point3' where
foldr f t (Point3 (x, y, z)) = x `f` (y `f` (z `f` t))
- Functor: A functor is a wrapper. Maybe is a functor, as are lists. The functor
type-class has one method:
class Functor f where
fmap :: (a -> b) -> f a -> f b
The fmap function applies the function to all instances of the inner type
throughout the wrapper, e.g. to every element in a collection. For example:
fmap (+1) (Just 6) == Just 7
fmap (+1) Nothing == Nothing
fmap (+4) [1,2,3] == [5,6,7]
- Merging Maybe:: There are many different ways to merge two Maybe
values in Haskell; here's a table (an asterisk indicates that the types must
be the same):
LHS | | RHS |
| >> | `mappend` * | `mplus` * |
Nothing | Nothing | Nothing | Nothing | Nothing |
Nothing | Just b | Nothing | Just a | Just a |
Just a | Nothing | Nothing | Just b | Just b |
Just a | Just b | Just b | Just (a `mappend` b) | Just a |
- Monoid: A monoid is really a pair of things: an associative operator and
an identity element. For example, + and 0, or * and 1, or ++ and [].
Because Haskell cannot have a type-class parameterised by an operator, for
types that can be part of multiple monoids, you use a newtype. Example
instance:
newtype IntAdd = IntAdd Int
instance Monoid IntAdd where
mempty = IntAdd 0
mplus (IntAdd x) (IntAdd y) = IntAdd (x + y)
- Traversable: This class is for everyone who ever wanted to use mapM
or sequence on a Map/Set/Sequence. I only ever use the mapM function from
this class, on Sequences and Maps.