One of the nicest things about Haskell is how much you can customize it
to suit your tastes. For instance, maybe you've been using Python for a
while now and you've admired its uniform notation for accessing elements
of all manners of sequences and mappings. When you switch to Java, it's
likely to drive you crazy that you can't foo[0] an ArrayList, only a plain
old array. You think "Gee, foo.get(i) and foo[i] are basically the same
concept, it's a shame I have to write it two different ways". With Haskell,
you can have all the type safety (and much more) that Java offers, with
the uniform notation that Python offers.
Haskell's type classes are very similar to interfaces in some other
languages (e.g., Java). When you say something is an instance of
a type class, what you're saying in Java-speak is that the class (Haskell:
type) implements (Haskell: is an instance of) the interface (Haskell: type
class). The key difference between a Java interface and a Haskell type class
is that you can make a type an instance of a type class after the
type has been declared. You don't even have to have the source. This will
be the key to our skinning exercise.
Let's start with dictionaries. Haskell 98 doesn't have a standard map/dictionary datatype like Python, but consider: what we're interested in is a consistent programming interface, we're not concerned with implementation. We might imagine a map implemented as a list of (key, value) pairs, or as a hash table, or some other data structure. Our API might look something like this:
class (Eq k) => Map d k v | d -> k v where
get :: d->k->Maybe v -- get a value for a key
set :: d->k->v->d -- set a value for a key
(!) :: d->k->v -- get a value for a key, error if the key doesn't exist.
-- Provide a default implementation based on 'get'.
m ! k = case get m k of
Just val -> val
Nothing -> error "No such element"
(//) :: d->[(k,v)]->d -- add/change multiple entries at once.
-- Provide default implementation based on 'set'.
m // ((k,v):changes) = set m k v // changes
m // [] = m
I used the functions (!) and (//) because they're used elsewhere in Haskell
98 with similar meanings. The (!) function returns the item at an index
- Python's foo[i] is Haskell's foo ! i. The difference is, in Haskell 98
(!) is defined only for arrays. We'll extend its usage here to maps of all
kinds. The (//) function takes a list of (key, value) pairs and adds/changes
each. Note that we've provided default implementations for (!) and (//),
but each implementation is free to override them if we wish.
The Glasgow Haskell Compiler
contains a data type called FiniteMap, which is an efficient map based on
binary trees. Here's what an implementation of our Map type class would look
like for FiniteMap:
import qualified FiniteMap as FM
instance (Ord k) => Map (FM.FiniteMap k v) k v where
get = FM.lookupFM
set = FM.addToFM
m // l = FM.addListToFM m l
Since our type class expresses ideas that FiniteMap was already built
to handle, the implementation is quite straightforward. Note however that
we use FiniteMap's addListToFM function to implement (//) instead of relying
on the default implementation. This is because addListToFM might be better
in some way than our implementation. For example, it might be more efficient
- perhaps it's written in C. Here's an implementation for association lists
(lists of (key, value) pairs):
-- Add or change a key,value pair in an association list
set_assoc [] k v = [(k,v)]
set_assoc (pr@(key, value):ps) k v = if key == k then
(k,v) : ps
else
pr : set_assoc ps k v
instance (Eq k) => Map [(k,v)] k v where
get m k = lookup k m
set = set_assoc
We had to do a little more work for this one, but the effect is the same.
Now we can use get, set, (!), and (//) on both FiniteMaps association lists
interchangeably.
This trick can apply to many more things. Iterators come immediately to
mind, as well as perhaps an instance of Map for arrays (since arrays are
just maps of indexes to values). Some useful type classes are provided as
part of GHC, like IArray, a type class for dealing with all sorts of arrays,
whether functional, mutable, or whatever. I'll add more examples to this
page as time permits.
