79

I'm going to be teaching a lower-division course in discrete structures. I have selected the text book Discrete Structures, Logic, and Computability in part because it contains examples and concepts that are conducive to implementation with a functional programming language. (I also think it's a good textbook.)

I want an easy-to-understand FP language to illustrate DS concepts and that the students can use. Most students will have had only one or two semesters of programming in Java, at best. After looking at Scheme, Erlang, Haskell, Ocaml, and SML, I've settled on either Haskell or Standard ML. I'm leaning towards Haskell for the reasons outlined below, but I'd like the opinion of those who are active programmers in one or the other.

  • Both Haskell and SML have pattern matching which makes describing a recursive algorithm a cinch.
  • Haskell has nice list comprehensions that match nicely with the way such lists are expressed mathematically.
  • Haskell has lazy evaluation. Great for constructing infinite lists using the list comprehension technique.
  • SML has a truly interactive interpreter in which functions can be both defined and used. In Haskell, functions must be defined in a separate file and compiled before being used in the interactive shell.
  • SML gives explicit confirmation of the function argument and return types in a syntax that's easy to understand. For example: val foo = fn : int * int -> int. Haskell's implicit curry syntax is a bit more obtuse, but not totally alien. For example: foo :: Int -> Int -> Int.
  • Haskell uses arbitrary-precision integers by default. It's an external library in SML/NJ. And SML/NJ truncates output to 70 characters by default.
  • Haskell's lambda syntax is subtle -- it uses a single backslash. SML is more explicit. Not sure if we'll ever need lambda in this class, though.

Essentially, SML and Haskell are roughly equivalent. I lean toward Haskell because I'm loving the list comprehensions and infinite lists in Haskell. But I'm worried that the extensive number of symbols in Haskell's compact syntax might cause students problems. From what I've gathered reading other posts on SO, Haskell is not recommended for beginners starting out with FP. But we're not going to be building full-fledged applications, just trying out simple algorithms.

What do you think?


Edit: Upon reading some of your great responses, I should clarify some of my bullet points.

In SML, there's no syntactic distinction between defining a function in the interpreter and defining it in an external file. Let's say you want to write the factorial function. In Haskell you can put this definition into a file and load it into GHCi:

fac 0 = 1
fac n = n * fac (n-1)

To me, that's clear, succinct, and matches the mathematical definition in the book. But if you want to write the function in GHCi directly, you have to use a different syntax:

let fac 0 = 1; fac n = n * fac (n-1)

When working with interactive interpreters, from a teaching perspective it's very, very handy when the student can use the same code in both a file and the command line.

By "explicit confirmation of the function," I meant that upon defining the function, SML right away tells you the name of the function, the types of the arguments, and the return type. In Haskell you have to use the :type command and then you get the somewhat confusing curry notation.

One more cool thing about Haskell -- this is a valid function definition:

fac 0 = 1
fac (n+1) = (n+1) * fac n

Again, this matches a definition they might find in the textbook. Can't do that in SML!

nbro
  • 15,395
  • 32
  • 113
  • 196
Barry Brown
  • 20,233
  • 15
  • 69
  • 105
  • 3
    About Haskell's interpreter, those are just some specific limitations of GHCi. Maybe some other Haskell interpreters might have better behavior. You can define values and functions in ghci, just not datatypes. You have to begin by saying "let". Also, every input has to be on a single line, so instead of the multi-line indent-aligned syntax for multiple defintions in "let", or for statements in "do" or "case", you have to separate them by semicolons, and surround it with brackets. e.g. "let { fact 0 = 1 ; fact n = n * fact (n-1) }". Also, "where" clauses don't work. It does get a little tedious – newacct May 01 '09 at 07:19
  • About the currying, SML has the same currying support and syntax as in Haskell. For example, in SML, "fun foo x y = x * y" is a curried multiplication. It's just that, for some reason, the convention in SML is that multiple arguments should be given as a single tuple argument rather than curried arguments. (Note that OCaml, however, follows the curried convention, unlike SML.) This is not an absolute rule, however; functions that are commonly partially-applied are curried in SML. For example, the "map" function in SML is curried, having the type "('a -> 'b) -> 'a list -> 'b list". – newacct May 01 '09 at 07:25
  • Haskell has both fixed precision integers (Int) and arbitrary precision integers (Integers) by default. It's just that when it's not constrained, the interpreter just chooses the widest possible one, which is Integer. However, some functions will only accept the Int, for example, the (!!) list indexing operator. – newacct May 01 '09 at 07:27
  • 11
    Unfortunately for you the fac (n+1) syntax is being removed from the next Haskell standard, so don't get too used to it: http://hackage.haskell.org/trac/haskell-prime/wiki/RemoveNPlusK – Ganesh Sittampalam May 02 '09 at 07:28
  • 1
    Hey Barry, I wonder what you think about this? http://tryhaskell.org/ I'd be interested to know if you'd consider using this online Haskell learning "engine" to teach students. I would help out if you wanted to alter it. Example of what's possible: http://tryhaskell.org/?input=sequence%20$%20zipWith3%20circle%20[20,50,80]%20[10,20,30]%20[10,10,10] More about the implementation here: http://chrisdone.com/posts/2010-04-05-haskell-json-service-tryhaskell.html I originally wrote it in the hopes that someone would use it for education. We could extend it so that hitting return means ';'. – Christopher Done Jul 17 '10 at 17:52

8 Answers8

92

Much as I love Haskell, here are the reasons I would prefer SML for a class in discrete math and data structures (and most other beginners' classes):

  • Time and space costs of Haskell programs can be very hard to predict, even for experts. SML offers much more limited ways to blow the machine.

  • Syntax for function defintion in an interactive interpreter is identical to syntax used in a file, so you can cut and paste.

  • Although operator overloading in SML is totally bogus, it is also simple. It's going to be hard to teach a whole class in Haskell without having to get into type classes.

  • Student can debug using print. (Although, as a commenter points out, it is possible to get almost the same effect in Haskell using Debug.Trace.trace.)

  • Infinite data structures blow people's minds. For beginners, you're better off having them define a stream type complete with ref cells and thunks, so they know how it works:

    datatype 'a thunk_contents = UNEVALUATED of unit -> 'a
                               | VALUE of 'a
    type 'a thunk = 'a thunk_contents ref
    val delay : (unit -> 'a) -> 'a thunk
    val force : 'a thunk -> 'a
    

    Now it's not magic any more, and you can go from here to streams (infinite lists).

  • Layout is not as simple as in Python and can be confusing.

There are two places Haskell has an edge:

  • In core Haskell you can write a function's type signature just before its definition. This is hugely helpful for students and other beginners. There just isn't a nice way to deal with type signatures in SML.

  • Haskell has better concrete syntax. The Haskell syntax is a major improvement over ML syntax. I have written a short note about when to use parentheses in an ML program; this helps a little.

Finally, there is a sword that cuts both ways:

  • Haskell code is pure by default, so your students are unlikely to stumble over impure constructs (IO monad, state monad) by accident. But by the same token, they can't print, and if you want to do I/O then at minumum you have to explain do notation, and return is confusing.

On a related topic, here is some advice for your course preparation: don't overlook Purely Functional Data Structures by Chris Okasaki. Even if you don't have your students use it, you will definitely want to have a copy.

Norman Ramsey
  • 198,648
  • 61
  • 360
  • 533
  • Nice answer! I'll take another look at SML. Mind you, my knowledge of both Haskell and SML are limited to a few hours each working through online tutorials and reading books. I have yet to dig into the finer details. Can you help with this question? http://stackoverflow.com/questions/740896 – Barry Brown May 02 '09 at 08:32
  • 13
    Agreed on everything apart from "print" debugging. You can use it in Haskell. There are both `Debug.Trace.trace :: String -> a -> a` and `Debug.Trace.traceShow :: (Show a) => a -> b -> b` available which go around IO() limitations and output the trace eagerly. – viraptor May 16 '09 at 15:59
  • 1
    < “ The Haskell syntax is a major improvement over ML syntax. I have written a short note about when to use parentheses in an ML program; this helps a little ” > : indeed… one of the thing I don't like with SML, is the ambiguity which can arrive with Case statements. I feel SML should require parenthesis around it, to avoid ambiguity when these are nested. Overall, the not so much big amount of griefs I have against SML, are all syntactical. – Hibou57 May 26 '11 at 20:45
  • 3
    you might want to check out the NICTA Haskell course for beginners: https://github.com/NICTA/course – Erik Kaplun Jan 31 '15 at 10:25
  • 3
    In recent GHC versions, the syntax for defining functions is identical (no more required `let`s). – ThreeFx Mar 21 '17 at 15:41
  • To expand on Norman's point regarding `print` - pervasive effects in Standard ML means beginners are less likely to take long, winding detours into topics like category theory, abstract data types, et al. – atravers Jul 27 '20 at 21:30
  • I find it bizarre that you think blowing student's minds is a *drawback*. – Marcel Besixdouze Aug 20 '22 at 12:58
29

We teach Haskell to first years at our university. My feelings about this are a bit mixed. On the one hand teaching Haskell to first years means they don't have to unlearn the imperative style. Haskell can also produce very concise code which people who had some Java before can appreciate.

Some problems I've noticed students often have:

  • Pattern matching can be a bit difficult, at first. Students initially had some problems seeing how value construction and pattern matching are related. They also had some problems distinguishing between abstractions. Our exercises included writing functions that simplify arithmetic expression and some students had difficulty seeing the difference between the abstract representation (e.g., Const 1) and the meta-language representation (1).

    Furthermore, if your students are supposed to write list processing functions themselves, be careful pointing out the difference between the patterns

    []
    [x]
    (x:xs)
    [x:xs]
    

    Depending on how much functional programming you want to teach them on the way, you may just give them a few library functions and let them play around with that.

  • We didn't teach our students about anonymous functions, we simply told them about where clauses. For some tasks this was a bit verbose, but worked well otherwise. We also didn't tell them about partial applications; this is probably quite easy to explain in Haskell (due to its form of writing types) so it might be worth showing to them.

  • They quickly discovered list comprehensions and preferred them over higher-order functions like filter, map, zipWith.

  • I think we missed out a bit on teaching them how to let them guide their thoughts by the types. I'm not quite sure, though, whether this is helpful to beginners or not.

  • Error messages are usually not very helpful to beginners, they might occasionally need some help with these. I haven't tried it myself, but there's a Haskell compiler specifically targeted at newcomers, mainly by means of better error messages: Helium

  • For the small programs, things like possible space leaks weren't an issue.

Overall, Haskell is a good teaching language, but there are a few pitfalls. Given that students feel a lot more comfortable with list comprehensions than higher-order functions, this might be the argument you need. I don't know how long your course is or how much programming you want to teach them, but do plan some time for teaching them basic concepts--they will need it.

nominolo
  • 5,085
  • 2
  • 25
  • 31
  • 3
    What is the difference between the (x:xs) and [x:xs] patterns? – Kiwi Dec 15 '10 at 16:13
  • 15
    `(x:xs)` matches a non-empty list, i.e., the argument must have type `[a]`. `[x:xs]` matches a singleton-list which contains a non-empty list, i.e., the argument must have type `[[a]]`. `[x:xs]` is the same as `[(x:xs)]` which is the same as `((x:xs):[])`. – nominolo Jan 15 '11 at 14:06
  • Re: "On the one hand teaching Haskell to first years means they don't have to unlearn the imperative style": I wouldn't take that for granted. Many students will already have some programming background from high school classes, graphing calculators, etc. – ruakh Aug 04 '13 at 00:33
  • 1
    Most of the pitfalls you mention reflect what 1st year students also experience when introduced to SML (except things like preferring list comprehensions over good library functions, since SML doesn't have either). It would have been interesting to know which Haskell-specific challenges they had, like dealing with laziness. – sshine Nov 12 '13 at 11:28
  • @SimonShine How does lazyness could be a challenge? – dawid May 31 '18 at 14:39
14

BTW,

# SML has a truly interactive interpreter in which functions can be both defined and used. In Haskell, functions must be defined in a separate file and compiled before being used in the interactive shell.

Is inaccurate. Use GHCi:

Prelude> let f x = x ^ 2
Prelude> f 7
49
Prelude> f 2
4

There are also good resources for Haskell in education on the haskell.org edu. page, with experiences from different teachers. http://haskell.org/haskellwiki/Haskell_in_education

Finally, you'll be able to teach them multicore parallelism just for fun, if you use Haskell :-)

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
  • 6
    I clarified my statement about the SML vs Haskell interpreter. Norman Ramsey said it better: copy-and-paste from the interpreter to a file. – Barry Brown May 02 '09 at 08:31
  • 4
    Note that SML is not just SML/NJ, which is single-core. Poly/ML supports native threads, and its Isabelle/ML add-on convenient abstractions like parallel lists and futures. – Makarius Nov 06 '15 at 00:29
13

Many universities teach Haskell as a first functional language or even a first programming language, so I don't think this will be a problem.

Having done some of the teaching on one such course, I don't agree that the possible confusions you identify are that likely. The most likely sources of early confusion are parsing errors caused by bad layout, and mysterious messages about type classes when numeric literals are used incorrectly.

I'd also disagree with any suggestion that Haskell is not recommended for beginners starting out with FP. It's certainly the big bang approach in ways that strict languages with mutation aren't, but I think that's a very valid approach.

Ganesh Sittampalam
  • 28,821
  • 4
  • 79
  • 98
  • 1
    I've seen some universities alternate between Haskell and ML, depending on who was teaching the course. – hbw May 01 '09 at 07:17
9
  • SML has a truly interactive interpreter in which functions can be both defined and used. In Haskell, functions must be defined in a separate file and compiled before being used in the interactive shell.

While Hugs may have that limitation, GHCi does not:

$ ghci
GHCi, version 6.10.1: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer ... linking ... done.
Loading package base ... linking ... done.
Prelude> let hello name = "Hello, " ++ name
Prelude> hello "Barry"
"Hello, Barry"

There's many reasons I prefer GHC(i) over Hugs, this is just one of them.

  • SML gives explicit confirmation of the function argument and return types in a syntax that's easy to understand. For example: val foo = fn : int * int -> int. Haskell's implicit curry syntax is a bit more obtuse, but not totally alien. For example: foo :: Int -> Int -> Int.

SML has what you call "implicit curry" syntax as well.

$ sml
Standard ML of New Jersey v110.69 [built: Fri Mar 13 16:02:47 2009]
- fun add x y = x + y;
val add = fn : int -> int -> int

Essentially, SML and Haskell are roughly equivalent. I lean toward Haskell because I'm loving the list comprehensions and infinite lists in Haskell. But I'm worried that the extensive number of symbols in Haskell's compact syntax might cause students problems. From what I've gathered reading other posts on SO, Haskell is not recommended for beginners starting out with FP. But we're not going to be building full-fledged applications, just trying out simple algorithms.

I like using Haskell much more than SML, but I would still teach SML first.

  • Seconding nominolo's thoughts, list comprehensions do seem to slow students from getting to some higher-order functions.
  • If you want laziness and infinite lists, it's instructive to implement it explicitly.
  • Because SML is eagerly evaluated, the execution model is far easier to comprehend, and "debugging via printf" works a lot better than in Haskell.
  • SML's type system is also simpler. While your class likely wouldn't use them anyways, Haskell's typeclasses are still an extra bump to get over -- getting them to understand the 'a versus ''a distinction in SML is tough enough.
ephemient
  • 198,619
  • 38
  • 280
  • 391
7

Most answers were technical, but I think you should consider at least one that is not: Haskell (as OCaml), at this time, has a bigger community using it in a wider range of contexts. There's also a big database of libraries and applications written for profit and fun at Hackage. That may be an important factor in keeping some of your students using the language after your course is finished, and maybe trying other functional languages (like Standard ML) later.

6

I am amazed you are not considering OCaml and F# given that they address so many of your concerns. Surely decent and helpful development environments are a high priority for learners? SML is way behind and F# is way ahead of all other FPLs in that respect.

Also, both OCaml and F# have list comprehensions.

J D
  • 48,105
  • 13
  • 171
  • 274
  • 1
    I have looked at both. One thing, in particular, bothers me about OCaml: the syntax for defining a recursive and a non-recursive function is slightly different. It seems trivial to us, but it can be a big thing to a beginner who's having enough trouble with syntax. I'm looking for tools that let me get right to the heart of the matter as quickly as possible. I haven't seriously considered F# since it's a Microsoft-only language. – Barry Brown May 12 '09 at 22:13
  • 1
    In OCaml, you use "let" for non-recursive value or function definitions and "let rec" for recursive definitions. In SML, you usually use "val" for non-function values and "fun" for functions except when they are non-recursive replacements of previous functions, in which case you must use "val" and a lambda function at the top level or "let val" and a lambda function if it is nested. Suffice to say, the OCaml way seems a lot clearer to me. Also, you do not have things like equality types to explain. – J D May 13 '09 at 07:39
  • Although F# is Microsoft only, you can write in the intersection of OCaml and F# whilst still benefitting from F#'s development environment. That would surely make life far easier for a beginner that having to battle with the comparatively-awful tools available for SML and Haskell. – J D May 13 '09 at 07:40
  • 1
    Also, it might be beneficial to students to have early contact with .NET libraries. Chances are, they end in some C# jobs later on anyway. While not really having invested much into alternatives, simple GUI programming is a plus for .NET (forms, wpf). Sometimes all you need is a form with an edit box and 2 buttons... With haskell, I would not be able to give a "first choice" advice on how to do that. (Web server libraries and html, maybe?). – BitTickler Sep 12 '16 at 17:47
5

Haskell. I'm ahead in my algos/theory class in CS because of the stuff I learned from using Haskell. It's such a comprehensive language, and it will teach you a ton of CS, just by using it.

However, SML is much easier to learn. Haskell has features such as lazy evaluation and control structures that make it much more powerful, but with the cost of a steep(ish) learning curve. SML has no such curve.

That said, most of Haskell was unlearning stuff from less scientific/mathematic languages such as Ruby, ObjC, or Python.

Nate Symer
  • 2,185
  • 1
  • 20
  • 27