When you’ve got a hammer…

… everything looks like a nail.
Type classes. In C#. Because I’ve got a hammer. Haskell gave it to me.

A what?

… a type class is a type system construct that supports ad hoc polymorphism. This is achieved by adding constraints to type variables in parametrically polymorphic types.

Uh… in English…

If you’re coming from an OO perspective (as I am) you can think of type classes as rather like an interface; with the additional ability to provide default implementations for their functions – making them perhaps rather more like abstract classes without fields in a type system with multiple inheritance, but that sounds more clunky.

Rather to be expected, the idea of type classes doesn’t fit well into an language based heavily around the OO-paradigm (like C#) but I thought it might be instructive (to myself even if to nobody else) to see how we could model something akin to them. What we’ll be doing below doesn’t interact nicely with inheritance and polymorphism, they they are somewhat orthogonal approaches and their intersection is at best confusing. It’s also a fairly terrible idea, but that should go without saying…

One way to describe what we’re trying to achieve would be to say that we wish to attach behaviours to a type not via its instances, but through a separate ‘static’ definition for that type. We also want membership to a type class to be enforced at compile time, and to be able to be members of multiple type classes simultaneously.

Equality for all (that opt-in)

I’m going to focus on the Eq type class here, but will be keeping in mind that this could be extended to other type classes.

First things first, we need a marker interface to declare our type as a member of the type class.

interface IEq { }

Next, we’re going to need a way for types to provide specific behaviour. It sound a bit odd to say, but inheritance seems the obvious choice here: it provides us the ability to override functions and is built-in to the language. One thing that we can’t restrict here is deriving classes adding fields/state – there are ways to enforce that but they’re more work and this is perfectly instructive for now.

public abstract class EqImpl<T>
    where T : IEq
{
    // we can require functions to be overridden...
    public abstract bool Equals(T a, T b);

    // or provide some default behaviour...
    public virtual bool NotEquals(T a, T b)
    {
        return !Equals(a, b);
    }
}

Now, we need a way for a type to actually specify its behaviour once it has declared itself a member of a type class, and a way for consumers of the type to access that behaviour.

For a class Foo we’ll do this by instantiating a ‘EqImpl‘ and storing it for use by consumers of Foo… this will obviously have to be done at runtime – if we could do this sort of thing at compile-time, we’d essentially already have our type classes already! The obvious place to do this is in the static constructor for Foo as we know this is going to be executed exactly once, and before Foo is ever instantiated. Again, we are unable to actually enforce that an implementation has actually been provided – we could provide a complete default implementation, but for simplicity I’ve not done that here.

public static class Eq<T>
    where T : IEq
{
    private static EqImpl<T> impl;

    public static bool Equals(T a, T b)
    {
        return impl.Equals(a, b);
    }
    
    public static bool NotEquals(T a, T b)
    {
        return impl.NotEquals(a, b);
    }

    public static void Register(EqImpl<T> impl)
    {
        Eq<T>.impl = impl;
    }
}
public class Foo : IEq
{
    static Foo()
    {
        Eq<Foo>.Register(new FooEqImpl());
    }

    public string Name { get; set; }

    private class FooEqImpl : EqImpl<Foo>
    {
        public override bool Equals(Foo a, Foo b)
        {
            return a.Name == b.Name;
        }
    }
}

Finally, let’s see how to actually consume our Eq ‘type class’

public void Compare<T>(T a, T b)
    where T : IEq
{
    if (Eq<T>.Equals(a, b))
        Console.WriteLine("equal :)");
    else
        Console.WriteLine("not equal :(");
}

Thoughts

One thing that was really highlighted for me, and something I’d never really thought about before, was that methods like

void M(IFoo a)

have lost an large amount of the compile-time type information that could have been available to them. To keep that information we can write

void M<T>(T a) where T : IFoo

which is likely almost exactly what we’d do if we needed T : IFoo and T : IBar; although given how much more verbose and clunky it is to write code like this, it’s fairly clear (a) why we don’t and (b) that it’s probably not idiomatic.


The more you know…

After writing this I discovered this correspondence of type classes using C++ template which shows some similarities to what I was attempting to achieve here using less powerful C# generics. So while all of this is all clearly a terrible idea, at least I feel that I’ve not inherently failed to understand type classes.