
From: Robert Dockins
Hello, I never liked the decision to rename 'map' to 'fmap', because it introduces two different names for the same thing (and I find the name `fmap' awkward).
As far as I understand, this was done to make it easier to learn Haskell, by turning errors like "Cannot discharge constraint 'Functor X'" into "X =/= List". I am not convinced that this motivation is justified, although I admit that I have very limited experience with teaching functional programming to complete beginners. Still, students probably run into similar problems with overloaded literals, and I think, that a better approach to problems like these would be to have a simplified "learning Prelude" for the beginners class, rather than changing the standard libraries of the language (writing type signatures probably also helps...)
This idea has been kicked around a few times, but, AFAIK, it's never really been fleshed out. Has anyone ever put anything concrete on the table? It seems to me that most complaints are about hard-to- understand error messages, and these almost always arise from typeclasses. They are especially confusing when they arise from syntax sugar. I suppose a prelude with no typeclasses and compiler options to make all syntax non-overloaded would be one way to start. On a related note, I've seen a number of Haskell design decisions justified by the "beginners find it difficult" argument. Is this argument really valid? Is there any reason not to just tell beginners to use Helium? Is there a case for something between Helium and full H98 (or H')? I have a lot of experience of teaching beginners--I've been doing it for years, and have seen the effects of introducing functional programming at various stages in the curriculum, and in various ways. That's given me a strong desire to ensure there is an easy learning path into Haskell. One thing I've observed repeatedly is that many students in later years, who learned functional programming early, have a strong impression that functional languages are only suitable for toy programs. Of course, that's because in their first courses they only SAW toy programs. But perhaps, they also moved on to Java as soon as they started to build GUIs, and as a result have the impression that you CAN'T build a GUI in the functional language they learned first. This is a very negative impression for students to have, and one that is hard to combat because beginning undergraduates are sceptical to functional languages from the word go: they "know" they really want to learn C++ or Java, and wonder initially what we're playing at. I combat that--successfully--by showing them early on that Haskell *is* a "real" programming language, in which one can build real applications. I tell them about darcs, or whatever new application is hot at the time. I show them how to build a GUI in wxHaskell--in the fifth week of their university studies. I want them to be frustrated at how difficult GUI programming is when they do it in Java (:-)! It works--some find it very hard, but many of the good students go much further than my lectures and exercises, and reappear in the third and fourth years as very expert and enthusiastic Haskell programmers. Now, this approach couldn't work if the language I taught really WAS only suitable for toy programs! Even a "beginner's prelude" would introduce a discontinuity for students, making it harder to take the step from course exercises to real programs, and that would mean that fewer of my students would end up as Haskell enthusiasts. I'd be very sad about that. On the specific point of map vs. fmap, it's true that similar difficulties DO arise for numeric literals, and a partial solution is to tell students to write type signatures at the beginning. However, this isn't a complete solution, because students still see plenty of class constraints in error messages, and they need to understand them sufficiently well to fix their errors. It's not too hard to explain that "Num a" means that a should be a numeric type--Integer, Double, i.e. a type they are familiar with. It's much harder to explain what "Functor f" means to a student who's still struggling to keep in mind the difference not just between a type and a class, but between a type and a value. The fact that f is a type constructor, and not a familiar type, is an additional obstacle. That makes overloading list operations much more of an obstacle for beginners, than overloading numeric operations is. The other thing to remember is that, even when students write type signatures, overloading ambiguities may still arise within their definitions. This is not usually much a problem for numeric types, because they are defaulted to Integer or Double. Making overloaded list operations easy to use for beginners would, I suspect, require defaulting rules for other classes--defaulting Functor and Monad to lists, for example. This is a whole new can of worms. John

On 2006-08-15 at 12:38+0200 John Hughes wrote:
From: Robert Dockins
On Aug 14, 2006, at 3:00 PM, Iavor Diatchki wrote:
and I think, that a better approach to problems like these would be to have a simplified "learning Prelude" for the beginners class, rather than changing the standard libraries of the language (writing type signatures probably also helps...)
This idea has been kicked around a few times, but, AFAIK, it's never really been fleshed out. Has anyone ever put anything concrete on the table? It seems to me that most complaints are about hard-to- understand error messages, and these almost always arise from typeclasses. They are especially confusing when they arise from syntax sugar. I suppose a prelude with no typeclasses and compiler options to make all syntax non-overloaded would be one way to start. On a related note, I've seen a number of Haskell design decisions justified by the "beginners find it difficult" argument. Is this argument really valid? Is there any reason not to just tell beginners to use Helium? Is there a case for something between Helium and full H98 (or H')?
I have a lot of experience of teaching beginners--I've been doing it for years,
I'm sure your observations are correct, and it makes a convincing argument, but I don't buy it completely.
[...] Now, this approach couldn't work if the language I taught really WAS only suitable for toy programs!
Granted.
Even a "beginner's prelude" would introduce a discontinuity for students, making it harder to take the step from course exercises to real programs, and that would mean that fewer of my students would end up as Haskell enthusiasts. I'd be very sad about that.
That would be something to be sad about if true, but it doesn't convince me that there is no solution. Here's what I would like to see: • Pare the standard prelude down to the bare minimum necessary to give types to the basic syntax of the language. So ‣ no Int or Double, just Integer and Ratio Integer, so that constants can be explained. ‣ Given that I'm accepting most of your argument, we'd have to have List rather than giving [|] and friends overloaded types -- I don't like that, but I can live with it. ‣ no operations on anything. (Are there any that absolutely have to be in the prelude?) • Move all the operations on Lists into List, all arithmetic into Integer, Int, Double, Float (or rather Arithmetic.Integer etc, and possibly have Arithmetic too -- being able to import a bundle of libraries at one go seems sensible). Similarly IO and so on. Now a typical Haskell programme would begin with with a whole bunch of import statements -- but then all but toy programmes do anyway, and using the same style doesn't seem to have caused C any problems. The "beginners' prelude" would then consist of several modules that provided classless versions of the troublesome overloaded functions, each to be replaced by the real thing when the source of the trouble had finally been taught. So what I'm proposing doesn't avoid your objection about a discontinuity -- in fact it introduces more, but my hope would be that several small discontinuities rather than one big one would be sufficiently little trouble. I'll grant that this is the "telling lies to children" approach to teaching, but teaching people Newtonian mechanics before Einsteinian is generally what happens, and the advantage is that where it applies it works almost as well as Einstein's version, and is practical. The same would be true of the beginners' prelude: folk who only got as far as doing arithmetic on Integers and Rationals with some simple stuff on Lists could go on using the simplest beginners' prelude indefinitely. True, you'd have to tell your students early on that they had to put some mumbo-jumbo (import Foo) at the beginning of their first programmes, but way back in the mists of time I was taught to programme in some language or other with just such an incantation, and I'm sure it caused no problems. Most students are quite happy to follow some instructions blindly at first (and the ones who aren't are usually capable of quickly understanding what the mumbo-jumbo does). -- Jón Fairbairn Jon.Fairbairn at cl.cam.ac.uk

Jon Fairbairn wrote:
...
The "beginners' prelude" would then consist of several modules that provided classless versions of the troublesome overloaded functions, each to be replaced by the real thing when the source of the trouble had finally been taught. So what I'm proposing doesn't avoid your objection about a discontinuity -- in fact it introduces more, but my hope would be that several small discontinuities rather than one big one would be sufficiently little trouble. ... folk who only got as far as doing arithmetic on Integers and Rationals with some simple stuff on Lists could go on using the simplest beginners' prelude indefinitely.
Hmmm. I clearly don't teach the way you do. "Folk who only get as far as doing arithmetic on Integers and Rationals with some simple stuff on Lists" doesn't include my students at the end of lecture 1! (That is, I start with different material). I actually don't see much problem caused by the overloading of numbers, where teaching is concerned. Yes, it means that students see classes very early-- in error messages at least--but it's enough to tell them that Num a means a should be some kind of number (Integer or Double in my course). This is a small cost at the time I have to explain it. The good thing about doing so is that students start getting used to the idea of classes, and to distinguishing a class from a type, so that when I later introduce Eq and Ord constraints (which are very hard to get away from in reusable code), the concept of a class constraint is already familiar. Later on, when I show them wxHaskell, there are classes everywhere, but the basic idea is by then quite familiar. So I'm dubious that your idea of a beginners' prelude would really work better, even for teaching beginners! Type classes are such an essential part of Haskell that even beginners need to learn about them... but constructor classes (Monad, Functor etc) are another kettle of fish altogether. John

On 2006-08-18 at 14:17+0200 John Hughes wrote:
Jon Fairbairn wrote:
...
The "beginners' prelude" would then consist of several modules that provided classless versions of the troublesome overloaded functions,
Hmmm.
I clearly don't teach the way you do.
Definitely! I clearly don't teach. (I'm unwell, condemmed largely to snipe from the sidelines)
"Folk who only get as far as doing arithmetic on Integers and Rationals with some simple stuff on Lists" doesn't include my students at the end of lecture 1! (That is, I start with different material).
That was really a guess about how your course must run, based on your argument.
I actually don't see much problem caused by the overloading of numbers, where teaching is concerned. Yes, it means that students see classes very early-- in error messages at least--but it's enough to tell them that Num a means a should be some kind of number (Integer or Double in my course). This is a small cost at the time I have to explain it. The good thing about doing so is that students start getting used to the idea of classes, and to distinguishing a class from a type, so that when I later introduce Eq and Ord constraints (which are very hard to get away from in reusable code), the concept of a class constraint is already familiar. Later on, when I show them wxHaskell, there are classes everywhere, but the basic idea is by then quite familiar. So I'm dubious that your idea of a beginners' prelude would really work better, even for teaching beginners! Type classes are such an essential part of Haskell that even beginners need to learn about them... but constructor classes (Monad, Functor etc) are another kettle of fish altogether.
Well, for you the "beginners' prelude" would only affect constructor classes, then. Actually, having thought about it further, I see a fundamental flaw in your argument. What it amounts to is that you /do/ want a beginners' prelude, but you want it to be the standard one. This does have negative effects. Partly because it's deeply ingrained, I find myself writing 'map' all over the place, thus constraining functions that would work on any monad to lists. I'm sure I'm not alone in this. I suspect that another part of it is that 'fmap' is a "noisier" name. I think Haskell should encourage people to think in terms of code that is as reusable as possible, so the "ordinary" map for Haskell should be the one for Functors. So I think what we should do (in this specific case) is essentially what Iavor suggested: provide a specialised function for mapping on lists and rename fmap. Quite what names we choose is another matter. A reasonable choice (forestalling the objection that using List.map, listMap or mapList would be too distracting for students) would be lmap:: (t -> t') -> [t] -> [t'] map:: Functor f => (t -> t') -> f t -> f t' Jón -- Jón Fairbairn Jon.Fairbairn at cl.cam.ac.uk

Hello,
On 8/15/06, John Hughes
... However, this isn't a complete solution, because students still see plenty of class constraints in error messages, and they need to understand them sufficiently well to fix their errors. It's not too hard to explain that "Num a" means that a should be a numeric type--Integer, Double, i.e. a type they are familiar with. It's much harder to explain what "Functor f" means to a student who's still struggling to keep in mind the difference not just between a type and a class, but between a type and a value. The fact that f is a type constructor, and not a familiar type, is an additional obstacle. That makes overloading list operations much more of an obstacle for beginners, than overloading numeric operations is.
I know this is not exactly accurate, but for beginners' purposes one could explain 'Functor' as a container type such as a list or a tree. In any case, if one wanted to avoid the issue, it seems entirely reasonable to define a function 'mapList' that has the list specific type. Most classes probably do this anyway. Students can then simply add it to the beginning of their programs, or (if they know about modules) create their own module, or they could import it from a predefined 'StudentPrelude'. (As someone else pointed out, this is not at all specific to Haskell: I've taken---and helped design---classes for Java and C++ which did exactly this.) When later in the class students learn about overloading, then the lecturer could tell them that the standard library already provides an overloaded 'map' function, that can be used for many different types, and they can start using it. Out of curiosity, how do people explain why 'map' is called that? -Iavor
participants (3)
-
Iavor Diatchki
-
John Hughes
-
Jon Fairbairn