8
votes

Consider a type class whose members are of type * -> *. For example: the Functor typeclass. It is a well-known fact that, in Haskell, there is a correspondence between this typeclass and its mathematical (i.e., Category Theoretic) analogue. Generalizing:

Question 1: Does every every typeclass in Haskell whose members are of kind * -> * correspond to some function between categories?

Now consider a typeclass whose members are of type *. For example, one could imagine a type class Group which corresponds to the category of Groups (technically, Group would be a subcategory of Hask whose objects comprise all of Haskell's types). Generalizing:

Question 2: Does every typeclass in Haskell whose members are of kind * correspond to some category (technically: some subcategory of Hask)?

From this, the next general question can be asked:

Question 3: Do typeclasses of kind equal to or higher than * -> * -> * correspond to some category theoretic notion?

And really, this entire question could be summarized as follows:

General Question: Does every Haskell type class correspond to some category theoretic notion?

EDIT: At the very least, it seems you could say that since every type class contains some set of Haskell types as its members, you could view every type class as some subcategory of Hask (closed under . and making use of id).

3
To what category theoretic notion does Foldable correspond?Zeta
@Zeta: Catamorphisms.Dietrich Epp
I'd say 2 holds true by definition of the term subcategory, but 1 and 3 seem quite far out to me. If I write some silly class whose only members are Proxy and Flip (Kleisli IO) Bool, then this is highly unlikely to have much mathematical meaning.leftaroundabout
@leftaroundabout, but wouldn't such a typeclass still trivially serve as some (bizarre) subcategory of Hask?George
@George: Not of Hask. Of the degenerate category (actually a monoid) of kinds, whose only object is * and whose morphisms are * -> * type constructors? Yes, I suppose again trivially. But that really is not very interesting.leftaroundabout

3 Answers

15
votes

When interpreted sufficiently pedantically, the answer to all of these questions is "yes", but for uninformatively trivial reasons.

Every category C restricts to a discrete subcategory |C| with the same objects as C but only identity morphisms (and hence no interesting structure). At the very least, operations on Haskell types can be boringly interpreted as operations on the discrete category |*|. The recent "roles" story amounts to (but is not spun as) an attempt to acknowledge that the morphisms matter, not just the objects. The "nominal" role for types amounts to working in |*| rather than *.

(Note, I dislike the use of "Hask" as the name of the "category of Haskell types and functions": I fear that labelling one category as the Haskell category has the unfortunate side-effect of blinding us to the wealth of other categorical structure in Haskell programming. It's a trap.)

Being differently pedantic, I'd note that you can make up any old crap as a typeclass over any old kind, with no interesting structure whatsoever (but with trivial structure that can still be talked about categorically, if one must). However, the classes you find in the library are very often structure-rich. Classes over * -> * are often, by design, subclasses of Functor, requiring the existence of certain natural transformations in addition to fmap.

For question 2. Yes, of course a class over * gives a subcategory of *. It's no problem to chuck objects out of a category, because the categorical requirement that identities and composites exist require morphisms to exist, given objects, but make no demands about which objects exist. The fact that it's boringly possible makes it a boring fact. However, many Haskell typeclasses over * give rise to much more interesting categories than those arising just as subcategories of *. E.g., the Monoid class gives us a category where the objects are instances of Monoid and the arrows are monoid homomorphisms: not just any old function f from one Monoid to another, but one which preserves the structure: f mempty = mempty and f (mappend x y) = mappend (f x) (f y).

For question 3, well, in that there's a ton of categorical structure lurking everywhere, there's certainly a ton of categorical structure available (possibly but not necessarily) at higher kinds. I'm particularly fond of functors between indexed families of sets.

type (s :: k -> *) :-> (t :: k -> *) = forall x. s x -> t x

class FunctorIx (f :: (i -> *) -> (j -> *)) where
  mapIx :: (s :-> t) -> (f s :-> f t)

When i and j coincide, it becomes sensible to ask when such an f is a monad. The usual categorical definition suffices, even though we've left * -> * behind.

The message is this: nothing about being a typeclass inherently induces interesting categorical structure; there is plenty of interesting categorical structure which can usefully be presented via type classes over all manner of kinds. There are most certainly interesting functors from * (sets and functions) to * -> * (functors and natural transformations). Don't be blinded by careless talk about "Hask" to the richness of categorical structure in Haskell.

6
votes

One of the problems here is that category theory, a.k.a. general abstract nonsense, is a theory that you can use to talk about almost anything in mathematics. So everything that we are talking about can be expressed using the language of category theory, but we might not produce any interesting results.

Does every every typeclass in Haskell whose members are of kind * -> * correspond to some function between categories?

No. This question contains a type error! A function maps sets to sets, but a category is not a set. (Put another way, functions are morphisms in the category Set.) Categories are formulated using classes, often proper classes, so you cannot feed a category to a function.

We would call objects in * -> * morphisms in the category of Haskell types. A subcategory of this category is the category of functors between Haskell types, which is called Functor.

Does every typeclass in Haskell whose members are of kind * correspond to some category (technically: some subcategory of Hask)?

Yes. This is true, but it's not terribly interesting. Just remove every object from Hask that is not in your typeclass, and remove any morphism in Hask that does not consume and produce elements from your typeclass, and you are left with a subcategory of Hask. This category should have at least one object, , and at least one morphism, id.

Do typeclasses of kind equal to or higher than * -> * -> * correspond to some category theoretic notion?

Yes. Again, this won't be very interesting. Let's take a typeclass X with kind * -> * -> *.

Is X an object in a category of typeclasses with the same kind? Well, yes. But this category isn't very interesting, because it's hard to imagine any nontrivial morphisms.

Is X a morphism in some category? No, because it cannot be composed.

Is X a functor mapping a subcategory of types in Hask to a subcategory of morphisms on types in Hask? Sure, but we would have to have some special knowledge that both X Y a b and X Z a b are permissible for the same a b before we allow morphisms into our starting subcategory on types of Hask.

This doesn't seem to me like it will produce any useful insights, which is not really surprising because we don't really know anything about X.


Conclusions

Category theory is one of those tools that is really quite easy to overthink and overapply. If you are not interested in category theory as a subject of study in and of itself, my recommendation is to find concrete motivations to use it. Specific typeclasses (functors, lenses, monads, comonads, etc) will sometimes provide you with enough structure or "raw mathematical material" from which you can construct an interesting proof in category theory. But the study of typeclasses in general may be a bit more abstract than it is useful.

3
votes

You can imagine mappings between categories that don't preserve their categorical structure. But they are not interesting. In category theory we want to work with structure-preserving mappings, and these are called functors.

A bare type constructor of the kind *->* has no provision for mapping morphisms. So the best you can do, as @pigworker explained, is to interpret them as functors from |C| to C, only because |C| has no nontrivial morphisms to be mapped.

Haskell Functor is an endofunctor, as long as it satisfies functor laws (which cannot be enforced in Haskell). An endofunctor is not an object in the category that it maps, therefore is not a type. But that's also true with morphisms -- they are not objects. There is, however, a way to represent morphisms as objects, if the category supports exponentials. Haskell's category of types is Cartesian closed, so it supports exponentials.

So does it also provide objects (types) representing endofunctors? As far as I know, it doesn't. So a functor is not a member of Hask (or whatever we call it).

Incidentally, a mapping of the kind *->*->* can have a nontrivial categorical interpretation as a bifunctor -- a structure-preserving functor from the product category CxC to C. See the definition of Bifunctor in Haskell.