In the spring semester of 2016, I will be a teaching assistant in the functional programming course at ETH, Zürich. In preparation I have been teaching Haskell to a good friend of mine. He's a mathematician. I looked forward to both my first experience teaching and to seeing how a mathematician would learn a functional programming language.
Note that he knows about programming. He has programmed in Java and C++, but paradigms other than object-oriented programming were entirely new to him.
Category theory
You would think that the most logical starting point would be to teach him category theory. Seeing as he wasn't already familiar with category theory, I opted not to do this. Instead, I started by introducing to the concept of types. Just like for most other people starting out with Haskell, there was no need to start with category theory.
Abstract concepts
Introducing types on an abstract level made us aware of the fact that values in Haskell be can only of a single type at the same time. This is great from an implementational point of view, but abstractly not really necessary from a mathematical point of view.
After a week long discussion and a blogpost, he finally acknowledged the existence (and usefulness) of partial functions. After that, most abstract concepts were very easy for him to understand: values, types, functions, immutability.
Typical concepts from mathematics weren't new to him and didn't require any more explanation: function composition, associativity, monoids, ... I was even able to explain the concept of currying to him in a single sentence: "The ->
operator is right-associative.".
Easy concepts
Some concepts don't exactly appear directly in mathematics but were still easy for him to understand.
Lists are a simple concept, they are like sets but ordered and without the requirement of unique elements. Type-classes with their laws are a little bit like algebraic structures with their axioms. 'An instantiation for a type class is then just an explanation as to why a type can belong to a certain algebraic concept', as he so eloquently puts it.
Strangely difficult concepts
Some concepts were easy for him, other concept were strangely harder. These concepts are rather easier for non-mathematician programmers than they were for him.
Lists also don't require an inherent equivalence relation defined on the elements. Because sets have the inherent requirement of their elements to be unique, the elements have to have an equivalence relation defined on them. Lists don't have this requirement and can therefore contain elements without there needing to be such an equivalence relation defined on them. As a consequence, mathematicians sometimes can't imagine that you would ever need to think about element without being able to assume that an equality is readily available. Simpler put: There's no Eq
in mathematics, so he sometimes looks at functions a -> b
as Eq a, Eq b => a -> b
.
The fact that 5
and Just 5
are of different types was strange to him. In pure mathematics, 5
is both an element of the set of integers $\mathbb{N}$ and of $\mathbb{N} \cup \text{Nothing}$. Note that c++ also has you believe that 5
and optional(5)
are equal due to the fact that their assignment operator are used in the same way.
Monads weren't as notoriously difficult to him as they are to regular programmers, he picked up an intuition fairly quickly. The do
notation, however, was strangely bizarre to him. The fact that do a ; b
is syntactic sugar for a >>= \_ -> b
seemed arbitrary.
Total functions
To get him familiar with working with monads and the do-notation, I had him implement a small expression evaluator. (Something that turns "5+7"
into 12
.) In less than an hour, he put together a badly-named function doEverything :: String -> Int
. As you can see from the type, this was not a total function.
It was strangely difficult to explain to him why it was important to make this a total function doEverything' :: String -> Maybe Int
. It became even harder once he noticed how much more complicated it would make his code. At first, that is. Once he'd implemented doEverything'
to be a total function using a lot of case ... of
expressions, we refactored the entire thing. First we changed most case ... of
expressions to use >>=
and lambda expressions. This was by far the hardest part in the learning process. Finally we rewrote the entire thing again, this time with the do
notation, using Maybe
as the Monad
it is.
Thank you
A big thanks goes out to my math-friend who I will not name for privacy reasons: Thank you for giving my first ever teaching experience and for patiently sticking with me through some of the initial struggles.