[Haskell-cafe] Factoring into type classes

wren ng thornton wren at freegeek.org
Tue Jan 20 21:23:10 EST 2009

Patai Gergely wrote:
> Hi everyone,
> I have a general program design question, but I can't really think of
> good examples so it will be a bit vague. There was a discussion on Show
> not long ago which brought up the problem that there are several ways to
> "show" a data structure, and it depends on the context (or should I call
> it a use case?) which of these we actually want, e.g. human readable
> form, debug information, serialisation for later reading and so on, and
> one of the solutions was to propose a family of show functions that
> carry the intended use in their name.
> However, there are other type classes that are too general to assign
> such concrete uses to. For instance, if a data structure can have more
> than one meaningful (and useful) Functor or Monoid instance, what should
> one do? Should one refrain from instantiating these classes altogether
> and just use the names of operations directly? If one still decides to
> pick a certain set of operations as an instance, what are the factors
> that should guide this decision? What about designing libraries, how
> much should one prefer standard classes for their interfaces?
> It seems to me that there is practically no literature on design issues
> like these, and it would be nice to hear some opinions from experienced
> Haskellers.

As others have mentioned or alluded to, I think that if you're designing 
general functions where you anticipate running into these issues then 
you should avoid the instance-selection mechanism of type classes. You 
can still use the dictionary-passing idea of type classes, but you must 
pass the dictionaries explicitly. This approach is used by the 
generalized functions in Data.List[1], as well as many other places.

Dictionary passing is, in essence, what higher-order programming is all 
about. Most often we deal with degenerate cases of dictionary passing 
where we only pass a single function (e.g. Data.List), but that's not 
the only way.

Another place where dictionary passing is used extensively is in the 
category-extras package[2] where the dictionaries are called 
"F-(co)algebras". An F-algebra is still degenerate as dictionaries go 
(it's a single function :: f a -> a), but it can be insightful to 
consider it as a dictionary proper, for example with "folding" functions 
like foldr. Normally we look at the type of foldr and think of it as 
taking two arguments, one for each constructor of lists. Instead, we can 
use a case expression to pack those two arguments together into a single 
F-algebra, selecting which argument to return based on the constructor. 
And this can be generalized to the folding functions for any datatype.

Thus, it's helpful to think of dictionaries as algebras (in generic and 
universal terms). A monoid is just one example of an object in universal 
algebra, it is an algebra defined by the underlying type, the operator, 
and the identity. Any type class is also an example of an algebra (e.g. 
Num defines an algebra with addition, multiplication, etc; Show is an 
algebra for showing). We can package any algebra up into a record and 
then pass that record around to the generic functions which need to use 
an instance of the algebra. The same idea can be used for passing around 
"laws" and "proofs" about data types (e.g. any function of type F(G a) 
-> G(F a) is a law that says we can distribute F over G).

The only difference between using type classes and manually passing 
these records around is that Haskell has linguistic and runtime support 
for plucking the records out of the aether based on types. Since the 
compiler knows about type classes it can do smarter things for them than 
just passing dictionaries, so they should be used whenever reasonable. 
Type classes are wonderful, but they're not a silver bullet. Even when 
they're not reasonable, having first-class functions means we're not 
limited by their restrictions.

[1] http://cvs.haskell.org/Hugs/pages/libraries/base/Data-List.html#22

Live well,

More information about the Haskell-Cafe mailing list