Haskell's type-classes (Was: Why did you learn OCaml?)

OK, trying the “Create new Topic” discuss feature, answering @sid from Why did you learn OCaml? (cc @yawaramin) :

Like anything there are always pro-and-cons I guess. It would be interesting to hear your perspective. Why do you think type-classes are a “toxic anti-feature” ?

This would be great in a separate (OT) thread perhaps.

(Of course, I was exaggerating a bit but)

In short, they favor writers over readers/debuggers/maintainers while decent software engineering should do the opposite: put some burden on writers to ease the work of whoever has to read & maintain the code.

On top of that, Types-classes turn large projects into big piles of instance resolution spaghetti that break a bit some of the advantages of static types w.r.t. large refactorings (that may be more due to GHC extensions to basic type-classes though).

One could say that the problem is not the feature but how people use it (e.g. in Haskell, inventing a teraton of crazy infix operators or 3-letter function names) but that’s too easy. I don’t remember who said it: “a system is perfectly designed to get the results it gets” … the idea is: towards what kind of behavior is the tool pushing programmers?

In comparison, OCaml modules reach a similar level of abstraction power (actually a much better one for the most common use-case: simple abstract types in .mlis) but impose some overhead for the more complicated cases (e.g. applying/naming functors) → it’s an “investment” in making code more understandable by other people (or the same people a few months/years later).

8 Likes

I’ll just note that this is exactly why the Google C++ Style Guide forbids the use of “C++ template metaprogramming”. It’s great fo the -writer-, hellish for the debugger-to-come. And I’m an experienced C++ programmer. I once tried to figure out how the “fbthrift” (Facebook’s internally-maintained version of Thrift) did their JSON wire-protocol support. It was not fun, and it was incredibly involved. For sure, an experienced member of that team, would already have the experience and knowledge to be able to debug/fix problems with that code. But an engineer from another part of Facebook? Not so clear at all.

The vast majority of programmer time is spent in maintenance. Languages, frameworks, and methodologies, should be aimed at that time, not at the time spent developing code. Unless, somehow, one can come up with a methodology that produces perfect code that will magically adapt to all future conditions and requirements, unmodified.

3 Likes

If you’d never used other people’s Haskell code, outside of the Haskell Platform, though - you’d make so much use of the standard typeclasses like Show etc., and wonder how anyone could think of doing without it.

Isn’t the only defense, to make features so painful or useless that programmers won’t overuse them?

donn@ obviously you’re 100% correct. But what you’re describing is a “hobbyist” language, not a language destined for real use in the real economy. And I think that if you were to ask the Haskell thought leadership if that’s what their intent is for Haskell, well, I think you’d get laughed out of the room.

People don’t design and implement signficant software artifacts, in teams, with hundreds of contributers, in order for those systems to never be used in commercial settings.[1] And the minute you hit a commercial setting, you hit the problem of “the last guy wrote this code, and we gotta maintain it”.

[1] Even in education this is a problem: In the late 80s I worked with a system, Nuprl, that had been written in the early 80s (maybe even 70s, Idunno) and as recently as the early noughties, that codebase was still alive-and-kicking. Needless to say, none of the original programmers were on that team. None of them.

1 Like

Actually the intent behind Haskell seems to have been a bit of a struggle for people.

What I was describing was sort of a joke. The point of it being, either you give it your best shot in endowing a language with powerful features that if used judiciously can make it very productive, or you stop short of that and make do with alternatives that are too cumbersome to seduce the programmer into overuse. Like, Haskell vs. OCaml.

Given the former, what the programmer team has to do is kind of take a cold shower and write them “artifacts” without going hog wild with all those cool features that are just begging to be used. OOP, templates, whatever.

I’m much more bullish on type classes than others it seems.

On the other hand, they make many large refactorings so much easier by making all the instance passing boilerplate implicit. It’s the same reason why type inference is good.

In many cases, type classes can code more polymorphic and usually that increases readability. It’s much easier to reason about a function Monad m => m a -> .. than a function 'a Lwt.t -> ...

Before discussing type classes at length, perhaps it’s worth step backing and seeing what’s our stance on the automatic elaboration of program terms. Personally, I think this kind of automaton is a good thing and I think far too many keystrokes are spent telling the compiler what’s already self evident. I’m not convinced that type classes are the best form of this automation, but it’s certainly better than nothing.

5 Likes

Assuming I understand the comparison you’re making, I disagree with you on this. Type inference is a convenience for something you could do yourself but choose not to. The type you would specify and the type that is inferred should be the same. And it’s the same in the call site as in the use site. Type classes are expressing something more than you could express otherwise. What is happening at the use site depends on what your program did to get there.

Perhaps you are saying “you could do the same thing just using first class modules and explicitly passing it around and it’s much more unwieldy”. To that, I think I agree.

In my opinion, I have sometimes felt the desire for type classes in Ocaml code. Perhaps it’s just because I don’t think in type classes, though, the times are very few and far between. For example, the semantics between various concurrency monads is sufficient that the amount of code that can be agnostics to the concurrency monad is fairly small, IMO. And a lot of that works just fine with functors as a concurrency monad consumes your entire program. I, personally, am ok with ppx_deriving for eq, and show, and ord auto code-generation.

1 Like