Fire up ghci. Make the text real big. Always show the types for everything as you go!
Takes ~1 hour generally.
Note: if you object to "uncertainty" (evocative of data/value flow possibilities), consider wording like "simultaneously possible values". It's a reference to how the compiler won't know whether Maybe a
is a Just a
or a Nothing
statically. Don't blather at me about dependent types; I know.
Alternate verbiage for uncertainty: product - simultaneous altogetherness, sum - simultaneous singlehood. Also consider what the category theoretic diagrams look like. Can be instructive.
Suggestions taken under advisement.
Explain how most REPLs only give you one mode of communication: function application and seeing the value returned. Demonstrate how ghci provides two modes: application -> value and expression -> type through the use of :t
Start with parametric polymorphism.
Write the type signature for id:
mysteryFunction :: a -> a
Explain that everything in Haskell defaults to the most general, polymorphic (if it can be) type possible when inference is allowed to do its thing.
Explain the inverse relationship between "type" and "term" spaces as a way of making free theorems relatable. Explain how rigidly polymorphic 'a' (not qualified by typeclass) is literally every type in Haskell that exists or ever could exist.
Explain how in some cases you can derive the terms from the types. This is one of those cases. There is only one valid implementation for mysteryFunction and it is id
. Get them to guess, but give the answer after a few moment's pause.
Demonstrate something not rigidly polymorphic. This one can be (+1) for your purposes but it doesn't really matter.
otherMysteryFunc :: Num a => a -> a
Explain Num and typeclasses briefly. (Abstract interface representing addition, subtraction, etc etc)
Explain how this changes the "type space" and makes it narrower because the set of types that implement the Num is smaller than the set of all types (rigidly polymorphic). Explain how by narrowing the type space we've opened up the term space from close to "nothing" to "nothing or methods in Num". Explain how the type/term space changes when you intersect the Num and Ord constraints.
Make a data type that's equivalent to newtype (don't mention newtype), single value. Preferably Bool. like:
data CoinFlip = CoinFlip Bool deriving Show
Demonstrate constructing and seeing the value. Start talking about products and sums now. Relate products to records/structs. Don't talk about deriving or "Show" or typeclasses yet.
Demonstrate a product type:
data DoubleCoinFlip = DFlip { frst :: Bool, secnd :: Bool } deriving Show
Make one. Demonstrate record upate if you care. It doesn't really matter.
Now make a Bool sum type.
data Bool = True | False deriving Show
Play with the values, explain how True and False are both valid inhabitants of Bool. Explain nullary constructors. Explain how it's 1 inhabitant + 1 inhabitant. Relate it back to the product and how it represents "and" and how the inhabitants are "2 * 2" inhabitants.
Explain how sum types are a toolkit for uncertainty, simultaneous possibilities.
Make a more appropriate type to drive the point home.
data CoinFlip = Heads | Tails deriving Show
Again, two nullary constructors. 2 inhabitants.
Make a custom list. We're explaining the combination of sum type, product type, parametric types, and recursive types.
data List t = Nil | Cons t (List t) deriving Show
Explain how the left side of the =
in a data type declaration is the "type constructor" and the right hand side is "value constructors".
Explain how it's non-deterministic and how the ND'ism is from possibly getting a result or not when recursing the list.
Explain how the Cons constructor is a product type because it includes a t
value and List t
. Mention again how sum types allow you to express and reason about uncertainty. Mention Haskellers will avoid unnecessary uncertainty (of cardinality or otherwise) in their data types.
Demonstrate List Functor
map (+1) [1, 2, 3]
fmap (+1) [1, 2, 3]
Show the result, show the types. Explain the typeclass constraint for Functor. Explain how Functor is strictly more general than list-specific map.
Demonstrate Maybe
data Maybe a = Nothing | Just a
Explain how this subsumes the need for null
. Explain how languages like Java and Python have implicit uncertainty (possibility of null
/None
) EVERYWHERE and in Python's case that's actually true for all types period!
Demonstrate Maybe functor
fmap (+1) (Just 10)
Compare with []
Explain Monoids. Explain binary associative operator. I usually relate it to the concept of "merging", but that's not strictly proper. Explain Monoid identity and how it can be used to pair off or null out "mappends".
mappend [1, 2, 3] mempty
mappend [1] [2]
Show the types for mappend and mempty! Explain how Monoids are like groups. Explain how Monoids guarantee associativity and identity. Mention that there are laws for other typeclasses, including Functor, too. Mention how commutative Monoids are like abelian groups and that reorderability of operations can be useful in distributed systems / concurrency.
Further modules/material to cover: explain applicatives in terms of List and Maybe. Tie applicatives in with monoid and take that to Monad and Alternative.