
On Sun, 27 May 2007, Andrew Coppin wrote:
Philippa Cowderoy wrote:
On Sun, 27 May 2007, Andrew Coppin wrote:
Seriously. Haskell seems to attract weird and wonderful type system extensions like a 4 Tesla magnet attracts iron nails... And most of these extensions seem to serve no useful purpose, as far as I can determine.
Yeah, who needed type classes anyway?
By which I mean that that's always been the way with haskell, and once you get what the extensions do they tend to in fact be highly natural - sometimes to the extent that people forget that they were ever an extension (constructor classes, anyone?).
Type classes are very easy to explain, and quite obviously useful. This, presumably, is why they're in the language.
You'd be surprised, I've seen people twist their brains in knots before finally getting it.
Almost all language extensions seem to be of the form "hey, let's see what happens if we randomly change the type checking rules so that *this* is permitted. What would that be like?" Usually it's an extreme struggle to even wrap my brain around what the extension *is*, never mind why this would be a useful thing...
The proposer almost invariably has a use case - often a pretty big one. Nor is the choice random! Often it's informed by type theory that precedes the extension by a good decade or two - how to handle it in an explicitly typed language is often well-trodden ground.
(OOC, what's a constructor class?)
It's a type class that's really a class of type constructors rather than one of types - if you like, a class whose parameter isn't of kind *. The canonical example is Monad. These days they're just type classes, there's no distinction made.
For example, GADTs let you implement monads as interpreters by defining a datatype representing the abstract syntax tree that describes a computation - you can't get this to type without at a minimum existential types and for many monad operations you need the full power of GADTs to declare a corresponding constructor.
I imagine it would never have occurred to you to try implementing a monad that way, right?
Indeed, I'm still figuring out why you would want to do something like this. (Beyond "because you can".)
It's much easier to understand than the 'traditional' style of implementation (which fuses the constructors with the interpreter). It's even more useful if you work with structures like arrows where there are useful things you can do with a computation without actually running it, as you don't have to bake the various analyses into the arrow type. I wouldn't chose it as the ideal release version, but I'd mechanically build the release code by doing everything with the GADT and then fusing once I'm done - and if compilers ever get good enough at deforesting, I'd happily elide that step! If you're fond of the Knuth quote, then the 'traditional' implementation may smell a lot like premature optimisation enforced by an inflexible type system.
Similarly, a lot of the developments with type classes and polymorphism have been about letting people write sufficiently general libraries - they're driven by the demands of code that people want to write, but often not so much by the demands of single, simple applications.
Well, sure enough I've run across useful things the type checker won't let me do. (For example, you can't make Data.Set into a monad, since it demands that all elements be in Ord, but a monad is required to accept *all* possible element types.) It's pretty rare though.
For me, working in Haskell 98, it tends to happen as my apps grow above a certain size (and it's less than a thousand lines). That, and I find the ST monad pretty invaluable so if I'm doing something where it fits I won't blink before using it.
Incidentally, Haskell 98 isn't that small a language itself - there's plenty of sugar around.
Looks like a pretty simple language to me...
It can be stripped significantly further. If you're not bothered about preserving the type system then it can go a long way indeed - you can retain pretty much everything with variables, applications, lambdas and simple let and case statements. Things like do blocks and list comprehensions aren't strictly speaking necessary at all, and an awful lot of complexity's added to parsing Haskell 98 by everything that can be done with infix operators.
I once read about this language called "C++". It's supposed to be like C, but better. And it is - if by "better" you mean, "exactly the same, but a lot more complicated". By comparison, Haskell is a *tiny* language. And yet, Haskell seems to allow you to express algorithms in a handful of lines of code that would take entire libraries in C, C++, Pascal, Java, Smalltalk, Tcl... and just about every other language I've ever seen in my life!
Seriously. Haskell seems to be pretty much the apex of programming. I can't imagine how you could make it any better. (Well, appart from a few trivial things like changing some of the poor name choices in the Prelude, and adding more libraries for "real world" stuff - but nothing really deep or fundamental about the language itself.) And yet, people keep adding more and more and more and more extensions. If it was that somebody added one extension that really transformed the expressiveness of the language, then fine. But on the contrary, it seems to be millions of little bits and pieces being added. It just seems messy and complicated.
You're missing a lot of the conceptual background - though I'm more than willing to just consider implicit parameters a bad idea! But an awful lot of the more popular extensions are primarily about relaxing constraints and picking the most natural way to follow through. It helps if you can picture a matching explicitly-typed language. An idea you might find helps: what you're finding with algorithms, I find with architecture when I have the extensions that let me type it. -- flippa@flippac.org "My religion says so" explains your beliefs. But it doesn't explain why I should hold them as well, let alone be restricted by them.