
On 06/02/06, John Hughes
Cale Gibbard wrote:
That said, I'd *really* like to see monad comprehensions come back, since they align better with the view that monads are container types, dual to the view that monads are computations, which is supported by the do-syntax. This view is actually much easier to teach (in my experience). Giving lists a little extra syntax is nice, but giving things unnecessarily restrictive types seems to be the point at which I'd consider it going too far.
The trouble with monad comprehensions was that it became far too easy to write ambiguous programs, even when you thought you were just working with lists. Haskell overloading works really nicely *as long as there's a judicious mixture of overloaded and non-overloaded functions*, so that the overloading actually gets resolved somewhere. Overload too many things, and you end up having to insert type annotations in the middle of expressions instead, which really isn't nice.
Lists are special, not least because they come very early in a Haskell course--or, in my case, in the first ever programming course my students have ever taken. Getting error messages about ambiguous overloading when they are still trying to understand what comprehension notation means (without even the concept of a for-loop to relate it to) is very destructive. And this is in the case where the code is otherwise type-correct--the kind of error message you would get by trying to append a number to a monad comprehension doesn't bear thinking about!
The class system is already something of an obstacle in teaching, because you have to mention it in the context of arithmetic (even if you tell students always to write monomorphic type signatures, they still see classes mentioned in error messages). After all, that is surely why Helium doesn't have it. I find classes manageable for arithmetic, even if students do take some time to learn to distinguish between a class and a type (or a type and a value, for that matter!). But it's a relief that list programs, at least, have simple non-overloaded types. List functions provide an opportunity to introduce polymorphism in a simple context--it's much easier to understand why (++) should have the type [a] -> [a] -> [a], than to start talking about MonadPlus m => m a -> m a -> m a.
There is a lot to learn in Haskell, especially in the type and class system. It's an advantage if you don't have to learn it all at once--if you can master lists and list comprehensions without exposure to monads (which are a much harder concept). We should never forget that beginners have somewhat different needs from expert programmers--and those needs are also important. If we want Haskell to be used for first programming courses (and it's a big advantage to catch 'em early), then there needs to be a learning path into the language that goes quite gently. Monomorphic lists help with that.
We did consider more aggressive defaulting to address the ambiguity problems with monad comprehensions--defaulting Monad to lists, for example, or user-declared defaulting rules--but this introduces yet more complexity without really addressing the problem of keeping types simple for beginners, so the idea was abandoned.
John
How about a compiler switch for beginners (maybe with an included script that adds it to the command line) that turns off a bunch of the more complex issues involved, and uses a beginner's version of the Prelude? Helium exists as well, which is a simplified version of Haskell for beginners, without even typeclasses. It has very careful and detailed error messages. Having monad comprehensions actually helps with another newbie problem, albeit one which is a little farther along the garden path -- learning about monads. Defaulting monad comprehensions is probably a good idea. List comprehensions are probably the most common case anyway, just because lists are the most common container type. :) This also further helps in introducing monads as generalisations of lists. If extensions to the language get standardised, I'd be fine with having monad comprehensions among them. Adding an extension declaration or compiler switch to any module using them wouldn't be so bad either, though I'd really like them in the actual language. I'd also want to include the usual changes to the prelude. The situation with map I find especially grating. Having two versions of common functions is one thing, but 3 is getting out there! :) Is explaining Functor really that hard? It's just container types with a way to apply a function to all of their elements. :) I don't think it should be necessary to completely rule out useful features because they might be difficult to newcomers. There should always be ways to turn things off, and construct a simpler language for new users. Dr. Scheme seems to take this approach, and has a pretty fine gradation of languages for leading users from their first steps into the finer points of Scheme. (I'm not completely sure how well Dr. Scheme works in practice as I haven't used it a whole lot myself, but the idea is great, and it certainly looks quite nice.) Also, it seems that classes are one of the first things one has to teach now anyway, as they're bound to come up in error messages in any actual programs you try to write. When teaching my friend, I taught the basics of classes along with types in the first and second lesson (including how to define them) and presented various values and functions as examples. Perhaps certain error messages would still be confusing after a basic intro to classes, but I think that improving error messages would help quite a lot here. :) GHC's ability to suggest common fixes is fairly useful (though occasionally misleading). An automatic (interactive?) glossary for terms like 'unresolved overloading' would be quite good. It would also be nice to offer links in error messages to the Haskell wiki, where users could discuss common reasons and fixes for a given error. - Cale