Monday, October 16, 2017

Is Haskell the right language for teaching functional programming principles?

The “park bench” panel at the Haskell eXchange last week talked about a lot of things, but some of them can be summarised by the question “Is Haskell the right language for teaching functional programming principles?”.

The point about this is that no one is in disagreement about how good a language Haskell is for doing real work – and that’s underlined by a meeting like the Haskell eXchange, which is primarily for people using Haskell in practice, or wanting to use it. Rather, the question is about whether the principles that Haskell is built on get obscured by the fact it’s a large and complicated language. You can see an example of this at the foot of this post: what you can see there is a perfectly plausible beginner interaction with ghci.

Some alternatives came up: there were advocates for Elm, http://elm-lang.org, and for PureScript, http://www.purescript.org, both of which are substantially younger than Haskell. It’s easier to see the attraction of Elm: it’s a lot simpler than Haskell, and so presents fewer impediments to beginners; it has also got a nice online environment. What are the downsides? While a number of books are in development (just google …) there aren't the mature texts that Haskell has. Also, it is arguably too tied to the “web development” paradigm, and so will skew learning towards that. 

PureScript also looks interesting, but perhaps more for developers than learners? While Elm is definitely simpler than Haskell, and will remain so, PureScript seems to be on a path to replicating as much of Haskell as possible, in the JavaScript world, so nothing particularly to be gained from making a switch. But to be clear, I’d be interested to hear arguments for PS that I might have missed … 

There’s a trend of fitting new syntax to old languages. Reason, https://reasonml.github.io, has done this for OCaml and Elixir, https://elixir-lang.org, for the Erlang VM. The languages are intended to be more approachable for those with experience of JavaScript and Ruby, but also benefit from their youth: libraries are less sprawling, and contributions more convergent; on the flip side, they can re-use all the previous developments and libraries for their host languages too. Still, neither seems to be an obvious answer for learners, and, of course, they are both impure and strict!

A final point: why not go for subsets of Haskell, like the Dr Racket approach, https://racket-lang.org? It would be interesting to see development of different preludes: most of the existing initiatives (just google …) seem to be designed to enhance the standard prelude, but working in the opposite direction might allow us to replicate the helium environment, for example. Another suggestion (due to Dominic Orchard) is to be able to close certain classes, so that there’s no way that an instance of Num can be declared for Int->Int, thus avoiding a set of error messages unhelpful to beginners.

What’s the best way forward? It’s hard to know. Maybe we should stick with the system we have, and for which we have some idea about teaching …  but it would be interesting to see how moving to Elm worked in practice in delivering effective functional programming teaching.

Full disclosure: at Kent, after teaching Haskell for many years, we currently a compulsory second year course on “functional and concurrent programming” using Erlang as the main vehicle, but introducing a little Haskell. An optional final year course on “programming language implementation” builds on a small but complete compiler written in OCaml. It’s possible to get started with Erlang quickly because it’s syntactically very lean, both for functional and for concurrent programming; the major disadvantage is that it’s substantially more difficult to do type-driven development.

There is some more material on the web comparing elm and PureScript (and others): 

Exploring the length function …




30 comments:

  1. Are you interested in collaborating using my work-in-progress Haskell dialect called Duet? http://chrisdone.com/toys/duet-delta/ I'm currently working on a semantic editor for it, in which one works with the AST rather than text, avoiding much of the friction of parse errors and other distractions. I plan to provide simple free-monad-like ASTs like a Terminal or UI or Graphics types to let people play with the idea of interpretation wherein the engine would run their ASTs like GHC runs IO. There's also the potential to compile Duet directly to machine code via GHC in order to run "fast" programs.

    ReplyDelete
    Replies
    1. Thanks, Chris. Looks very useful as a tool to get beginners started!

      Delete
    2. Hi Chris. Is Duet open source ? I am really interested in semantic editors and would love to take a look at it !

      Delete
  2. Thanks for the write up. Interested to know that you teach functional language using Erlang. You mentioned about its disadvantage to do type driven development. Can you expand a bit on that?

    ReplyDelete
    Replies
    1. Because the system itself is dynamically typed, there's no chance of getting type errors from the compiler itself. There is a separate tool, dialyzer, which will do post hoc checking, but it tends to be used for heavier-weight systems.

      Delete
  3. Chris Done's code.world must have come up in the discussion, right? That may be an indicator that a subset of Haskell can be a good teaching language?

    ReplyDelete
    Replies
    1. No, it didn't … thanks for the mention, and here's the link https://code.world

      Delete
    2. Oops, have to make a correction to my comment ... code.world Chris Smith's project. Here's a talk he gave about teaching it to schoolkids: https://youtu.be/7CGuI9HcfqQ

      Delete
  4. I have to say that Racket was made for this and would seem to be a great language to use. Plus they do have the Academic Might of Matthias Felleisen and his family of PhD students that still are developing the language.

    ReplyDelete
    Replies
    1. Yes, Racket certainly has the nested family of languages. What I'd miss out of the box is strong typing, though …

      Delete
    2. I'd take a look at http://docs.racket-lang.org/hackett/ ! I TA the course at Northeastern University that Felleisen pioneered, and work in the Programming languages lab with him and the racket team. I've been considering making a set of beginner languages with hackett's type system and laziness that follows the beginner -> advanced student language progression. Also worth checking out Pyret. It's currently under development by another Northeastern professor, and is looking to replace the racket based curriculum in the near future. It draws inspiration from Haskell, Racket, and Python.

      Delete
  5. My Standard ML teacher back in the day, Nils Andersen, had some really salient points about teaching languages.

    First, there must be a decent implementation. It must run on several different systems, and shouldn't force students into one particular environment too much.

    Second, good documentation must exist for the system. This includes the system manual, the standard library, and good material in form of books written.

    Third, it should be possible to introduce material in layers. For Standard ML, you want to avoid the introduction of the module system in the first few weeks, as that system is fairly complicated.

    Fourth, it should be possible to describe many different programming paradigms easily. Either by implementation of the paradigm in the language, or though modest extensions of the language.

    Fifth, the system must have great and precise error messages with little room for misinterpretation by the student. Learning new stuff is much harder if notation is imprecise, and if your cognitive load is already there with the problem solving, you don't want to add additional load on top.

    Sixth, there must be enough libraries to build an interesting curriculum. Students tend to be far more involved if they have something tangible they can interact with. Don't underestimate bragging rights too. People can show what they've made to others. And if it is understandable to other people, the subject becomes far more interesting.

    Haskell solves some of these points well, but I think it is fair critique to say that other problems are harder to avoid. The heavy reliance on type classes means you have to introduce the numeric tower early on, and thus you end up in a discussion of type classes. The Foldable instance is another valid point for the length function. The lazy-by-default evaluation model means that some programs may require more explanation. That is, while the program is correct, the student must understand space-behaviour. I think eager evaluation tend to have a simpler model there (However, sometimes that argument holds in the other direction: an eager program infinite-loops where the lazy program doesn't).

    Many Haskell programs are outright daunting to the newcomer. They introduce a deep abstraction by means of applicatives, prisms, lenses, monads, profunctors, and so on. If a student grabs such code after a course, it can feel like an insurmountable wall. Haskell, compared to its other functional languages, tend to be heavy on operators and supercombinators. This makes for concise and symbolic programs. However, the price for the succinctness is that readers have to understand more.

    It also means you have students who are able to run circles around even seasoned TAs. This can be quite the challenge. Racket solves this by introducing the language in parts, so you can't abuse a Functor or Traversable instance in the second week when it is meant that you use lower-level features.

    As for your comments on Erlang, I tend to agree since I like static typing as well, especially in the situation of teaching. To play the devils advocate, it is sometimes also needed that you execute programs in an untyped system. This can explain why things and go wrong and the errors which occur can be more eye-opening as to why something is misbehaving. If not for anything else, you can appreciate the power of a proper static type system, be it Hindley-Milner, Dependent, System-F, etc.

    ReplyDelete
  6. Must it be a single language? Might one not start with a pure functional language without a type system like Haskell's and then graduate to Haskell once the foundations are mastered?

    ReplyDelete
    Replies
    1. This is an excellent idea, but it requires some effort as you must provide enough tooling for your proto-Haskell language such that students feel they can achieve results in the limited language. Otherwise, people will feel they are given toys for toddlers, not toys for grownups.

      Delete
    2. Yes, I guess one route is Haskell via Elm, and it would be interesting to hear if anyone has tried that.

      Delete
  7. I have been teaching FP using Agda the past three years or so, with pretty good results (though students complained a lot about relative lack of online resources). This year, I decided to teach the first third of the course or so using Haskell, and then switch to Agda. Now, there was definitely a learning curve for me to figure out how to teach the interesting type classes, particularly monads. Hopefully I will do this better in subsequent offerings. But I can say it was much harder on the students to deal with the extra complexities of Haskell, particularly the abstractions like functors, applicatives, and monads. Grades are worse, and student frustration is higher. So I think it is a fair point that Haskell right out of the box is maybe too complex for students who have not studied FP before, at least at a school like Iowa with a range of students (maybe at MIT or UPenn things would be different?). The idea of adapting the Racket approach to Haskell is very appealing. (And with Dependent Haskell, maybe we can just start with a simplified version of Haskell and go all the way to dependent types! :-))

    ReplyDelete
    Replies
    1. Yeah, IMO the standard Haskell typeclassopedia is a distraction when you're trying to learn FP. It turns a lot of people off. If I was designing a first-year course I would leave them out and focus exclusively on the standard functional principles (immutability, recursion) and building a mental model of the HM type system.

      Of course, typeclasses are a very cool feature but they shouldn't be introduced through the regular Haskell library classes, they should be brought as and when the students feel the natural need to abstract the same operations over multiple types.

      Delete
    2. Intrigued to hear that Agda went better … I can see that it's not so well supported, but there is a good book out there, I believe :-)

      Delete
    3. I had much the same experience when I was at Birmingham.

      Agda is much more uniform than Haskell, and the absence of things like nontermination and seq mean you can tell fewer lies about what is going on. Eg, you can teach how to prove programs correct by induction without having to handwave about nontermination. (To clarify, I mean pen-and-paper proofs, not Agda-based very dependent types.)

      In particular, Agda has a pretty good termination checker, which means you can use it as a vehicle for Turner's "strong functional programming" without too much trouble.

      If Agda's codegen were a bit closer to production grade, I would strongly suggest replacing Haskell with it as a vehicle for teaching functional programming. As it is, I think the case is arguable but not a slam dunk.

      Delete
    4. Interesting … any thoughts about whether Idris would have been (more) suitable?

      Delete
    5. I don't know, since we didn't try it! However, we didn't try it because at the time, Idris seemed even even more "researchy" than Agda. For example, the parser and syntax seemed to have more ad-hoc bits than in Agda (mostly to make it look a bit more like Haskell).

      One thing I liked in Agda (as opposed to Idris, ML and Haskell) is that all polymorphic quantifiers have to be explicitly specified. My personal suspicion is that if you are teaching how polymorphism works, it's actually helpful to start out making it fully explicit and then gradually introduce techniques for making it implicit. (It's hard to say for sure because the students had already seen Java, Ocaml and Haskell by the time they met Agda...)

      Delete
    6. Yes, it can be a very good thing to be explicit: saving a few keystrokes is never a strong motivation.

      Delete
    7. One particular things that has always been a thorn in my eye here is something like a function 'substring : string -> int -> int -> string' in Haskell. The two integers are position and length, but which is which? In OCaml, you can do labeled arguments and type it as 'string -> pos:int -> len:int -> string' and a call is 'substring str ~pos:4 ~len:10'. In fact, some of my Erlang code resorts to 'sub_string(Str, #{ pos => 4, len => 10 })' for the interface in order to make it hard to avoid the confusion. Also consider '#{ private_part := Priv, public_part := Pub } = crypto:keygen()'. Accidentally switching those types around is quite dangerous.

      In contrast however, the verbosity of something like Java overboards itself with explicitness. It becomes a tedious rote to the point where IDEs generate the code for you. Once this happens, errors are bound to happen because you generated the wrong code with a click and your mind doesn't realize the wrong thing is there!

      As for Agda vs. Idris, I recently toyed with both and I think they are about the same level in how researchy they are right now. But Neel's point about Agda being more explicit is an excellent one.

      Delete
    8. Yeah, OCamlers tend to use labelled arguments and Haskellers I would say tend to use newtype wrappers (at least it's a recommended best practice) so you'd see types more like `substring : String -> Position -> Length -> String`.

      Delete
  8. Haskell originated from research. So it's not like C, Clojure that design for production. All language learning curve are relative output per unit of time spent. I guess the problem is 'how to find good resource to learn haskell and make it productive than C, Rust etc' or at least some cases of DSL that work primarily for haskell but not the others etc.

    I just finished reading 'Learn You a Haskell for great good'. It's easy for me to spot Tuple is not foldable but List does.

    Haskell need simplicity, more interesting use case. Cuz, simplicity drive community to grow. So does good examples, books, library etc.

    ReplyDelete
    Replies
    1. It's not quite a simple as to say that it originated from research. There was an active standards process at the start of its life, but latterly it has been driven more by community enthusiasm and contributions to GHC, which has been an extraordinary phenomenon. But, it has continued to grow …

      Delete
  9. Coming from C to Java to Python again to Java and currently developing almost everything in JavaScript/TypeScript in an mostly functional style I can say the biggest problem for me learning Haskell is to get all the knowledge together to "get things done".

    If I compare this with other programming platforms like nodeJS were the hello world example is a web server and compare this with Haskell where their example is quicksort, the intention is a totally different. With the first example I can solve a real life problem with the second one "oh great I can also sort stuff in Haskell as I can in JavaScript". That is not my opinion but I can understand if people give up in further learning and choose languages like Go or NodeJS.

    I have no doubt that Haskell has a bright future. I haven't programmed anything until now in Haskell but it has all the strengths you want from a modern programming language to solve complex problems.

    So skipping some of the amazing part like the static type system seems wrong to me. From my experience in the JS community I can say that the trend goes always in the direction of strong typing.

    ReplyDelete
  10. It depends. Do you want to teach static typing as well? Or dynamic language? we use haskell at the argentine university UTN and we use it as a vehicle to teach static typing as well in constrast to the dynamism of smalltalk. So the sometimes baroque error messages are part of the learning, so to speak. Besides it's purely functional.

    If you prefer dynamic, I agree, scheme/racket is a great choice, and ultimately it opens the door for learning how to do fun things in a dynamic, live environment, not unlike Smalltalk.

    ReplyDelete
  11. The problem does not seem to be the Haskell language itself, but the fact that the standard library relies too much on relatively advanced concepts (typeclasses, folds, monads, ...).

    I suspect that Haskell endowed with a watered down, "first-order" standard library, (built exclusively upon inductive data-types, recursion, pattern-matching, and a few reasonable typeclasses (Eq, Ord, Show)) should provide a good environment for teaching.

    ReplyDelete