Teaching Haskell means teaching important concepts

I am teaching Haskell in the 1st semester CS master programme course Concepts of Higher Programming Languages at the FHV. Why did I decide for Haskell and not Python, JavaScript (JS), TypeScript (TS), Scala, Erlang, Rust, Your Favourite Programming Language?

At this point I assume and expect that students are not only familiar with mainstream object-oriented programming (Java, C#) but that they more or less excel in it - they don’t have to be experts (nor can they, unless they have worked for many years in the industry), but they should be very confident and be able to write larger, complex applications in a proper OO way.

Getting into another OOish language like Python, JS, TS would be simply a waste of time. Therefore, I was contemplating teaching the central concepts of Haskell, Erlang and Rust. For Rust that would be its borrowing feature and how it achieves safe memory management, for Erlang that would be Actors and shared-nothing-semantics message-passing; and for Haskell… well it would be Monads but for the students to fully get it I realised that I would need to give a proper introduction into the whole language (following along the lines of Graham Huttons brilliant Book Programming in Haskell (2nd ed.) and extending it).

So I decided to go for “Haskell only” and it turned out to be the right decision, especially for a course on Concepts of higher programming languages. Haskell allows to discuss many concepts in the context of a pure functional language which makes it especially suitable for such a course. Of course, some concepts the students “know” from OO as they have found their way into mainstream by now but the way these concepts are presented and need to be dealt with in Haskell is in such a pure way that students will learn and get the most out of it. Sure, I could have also decided to teach the concepts in JS but they would come across as muddled and not very clear - and some of these concepts would simply not have been possible in JS (or would have been a crude and ugly workaround, with the deeper idea completely hidden underneath).

I am very well aware that very few (none) of the students will pick up Haskell later in their day-to-day life as software engineers, however it is my firm belief that the way this course taught them these concepts, will have made them much better developers, because they have a broader and deeper understanding of them. The main concepts I chose to teach are:

  • Static Types, immutable data, basic functions. Dealing with a really strong static type system (yes I know Java is also statically typed but as long as you have dynamic dispatch there is some runtime-type information around…), immutable data and pure functions is a challenge on its own, forcing the students to think very different about what they know so far.

  • Explicit Recursion. No more mutable data, therefore we employ recursion instead of iteration and in-place memory updates. To think recursively takes a while but is probably among the most important abilities for computer scientists and it can never hurt to reiterate it.

  • Higher-Order Functions. Probably the most essential concept to learn in this pure form in Haskell. The way it works in Haskell in combination with Currying and Lambdas is tremendously powerful, which is also realised by students. This concept has entered mainstream OO languages by now, therefore students are able to pick it up quickly and see why it is useful.

  • Lambdas. Have arrived in mainstream OO as well and students quickly understand the use of anonymous functions to write concise code.

  • Currying. Tremendously powerful concept, not available in this form in mainstream OO - yes you can hack a workaround which looks like currying but the way it comes across in Haskell is simply beautiful. When telling students that it can be understood as Dependency Injection the penny drops.

  • Data Definitions. How can we define data types when there are no objects? ADTs is a very valuable lesson to learn, especially to teach recursive data types.

  • Pattern Matching. Probably the most elegant feature of functional programming in general and invaluable to write concise recursive functions. It is still a mystery to me why Java has not properly implemented pattern matching yet.

  • Lazy Evaluation. The concept of separating the producer from the consumer is extremely powerful and important on its own, left alone infinite data structures and functions on them. These concepts can be emulated to some extent in OO languages (for example with an Iterator or Streams), however it is nowhere as compelling and pure as in Haskell.

  • Type Classes. To show students that polymorphism is not a unique feature of OO but that it is a fundamental concept allowing to express abstract concepts, facilitating code reuse (among others) and allows to express laws and concepts of a specific type. Students generally understand Type Classes as a kind of interface (in Java terms), which is fine for me and not far from the truth.

  • Functors. Seeing how to apply already existing functions on types with some structure is a valuable insight. However I do not go too much into the “theory” and leave it at a type with some structure which can be mapped over - this generally blows the students mind anyway.

  • Applicatives. Probably one of the hardest sells, I motivate it basically through currying within a functor. It is there, where students should get a glimpse what effectful programming means and how it can be achieved in a pure functional language.

  • Foldable and Monoids. Getting more abstract, but folds and monoids have entered mainstream OO in functional stream processing, so students understand that it is useful. However the reductionist way that Haskell captures these concepts is perfect to explain it from first principles. Also I think every computer science student should know and understand what a Monoid is.

  • Monads. I spend quite a lot of time building up to and motivating Monads as they capture everything learned so far and allow me then to talk about IO, Concurrency, STM and Property-Based Testing all of which build on Monads in some way. I am not using the do-notation straight away but am forcing them down the way along the »= operator to make it really explicit. The main point I make and I discuss at length here (and in IO) is side effects. When you come from a mainstream OO language, your idea about side effects is rather implicit and you have probably not thought about it in such an explicit way as you were forced to when dealing with it in Haskell. This is what I subject the students to and I think it is absolutely worth it: being more explicit about side effects in your thinking, reasoning and your code ultimately leads to better developers and better code.

  • IO (and do-notation). After pestering the students with explicit »= I show them the do-notation and how to do “real-world” stuff with IO. At this point this is a huge relief for them as they have been wondering for some time now where the promised real-world applicability of Haskell is. The more important insight I discuss and students take away from this is that there are “impure uncontrollable” (IO) and “pure, controllable” (previous Monads) side effects - again this will broaden their horizon and allows them to think even more explicitly and detailed about side effects.

  • Concurrency. Is generally a very poorly treated topic in CS studies. Students learn how easy it is to write concurrent programs in Haskell using IORef, MVar and Async/Wait.

  • STM. To show students a very different approach to concurrency, making another showcase for the amazing type system of Haskell and how it deals with side effects.

  • Property-Based Testing. To show students that there is more than tedious Unit Tests (they should be already familiar with). Expressing functional specifications directly in code blows their mind.

  • Dependent Types. Going beyond Haskell and showing them whats next and what extremely powerful concepts are lurking in Dependent Types (I am using Idris): Types as 1st class Citizen, Totality, Programs as Proofs, Dependent Functions, Dependent Pairs, Equality as Type, Philosophical Foundations. So students have a rough idea what is possible but which will take decades to arrive in mainstream (if at all).

What about the future of the course? I can imagine that I am going to try out teaching Erlang in this course instead of Haskell and see how it goes down - or Rust - or Scala - or Clojure. Each of these languages has unique concepts and also allows to cover a few of the concepts I am teaching with Haskell. However, the problem is that to teach a language and its concepts you need to have mastered it both conceptually and in a real-world context, otherwise the teaching experience will be a shallow one. Unfortunately I can only say that about Haskell, Erlang and Java and I would need to fully and deeply learn other languages if I aim on teaching them. Unfortunately I doubt that I will have the time for that undertaking as it is substantial, therefore I will probably continue with Haskell as it is the language I am very familiar with and will stick with for the next years for real-world software engineering research. Still, just to get some fresh air into the course I think at one point I will try to go with Erlang or Elixir, which would also allow me to capture OO concepts from a functional perspective (how we can emulate subtyping with actors for example).