I found a link to an OSCON 2007 lecture on Haskell by one of its authors. It offers a good introduction to the language and an insight into its creation and development. The lecture also references a paper about the history of the language.
The history of Haskell is somewhat chaotic. A disparate collection of functional programming advocates got together and stopped working on their individual projects to focus on a single language. It seems every step required a meeting, a consensus, and then an abandonment of that decision in practice in favour of some polar opposite. They even named the language Haskell because the name everybody liked (Curry) was too open to jokes to make it a sensible choice in practice. This is a pattern I’ve noticed before in design. It is neatly captured by the saying: “no plan survives first contact with the enemy.”
One principle they adopted early in the language design was laziness. This means that values are resolved only when needed. This is useful since you can then define data larger than your computer’s memory capacity, yet your program will still work so long as you don’t try to resolve too much at any point in time.
A clear difference between Haskell and the Lisp family of languages is its type system. Haskell infers types where possible but you can also declare them to document and automatically check your assumptions. Haskell also has type classes which describe functions that work on types in a similar fashion to object oriented classes. However, unlike object-oriented languages, Haskell passes these separately from the data that they work on. Types also keep state changing functions like IO from being called from within pure functions.
A pure function is a function whose output is entirely dependent on its input. Pure functions are useful because you can safely reason about their properties and use the substitution model to understand them. The substitution model means you can substitute any function for its result and the rest of the program remains the same. As a result it doesn’t matter which order Haskell resolves its values and can also resolve functions in parallel.
IO functions like reading input from a keyboard or writing on a screen rely on creating side effects. You cannot substitute the result of an IO function because you will not replicate the changes to the state of the computer. Order is also important with IO functions. Haskell cannot resolve the dependence of IO functions in the same way it can for pure functions. Also, with lazy evaluation, a function with no input or output (only side effects) might not resolve at all.
But IO functions are essential. As Simon says in his lecture, “without them, all we have is a box that gets hot.” The property that IO functions share (and pure functions don’t) is that the results depend on the relative point in time that you resolve it. Haskell addresses these problems by passing around tokens to represent the effect a function will have when performed. In other words it extracts the time component from the problem.
This token is called an IO monad. A monad is a particular type of functor (that is a function that maps one set of data to another.) In this case it takes the current state of the computer as input and produces a new state and some data as output. You can also use monads for pure functional purposes, but here they keep the IO parts and the pure functional parts of Haskell programs apart.
Calling a state modifying function from a pure function pollutes it in the sense that it is no longer safe to reason about it using the substitution model. And this pollution propagates via the data throughout the program. However, you can safely call pure functions from within procedural ones by using IO monads and Haskell will enforce this restriction for you through its type system.
In his lecture, Simon visualises Haskell programs as a core of pure functional code surrounded by a thin crust of imperative interfaces to the outside world. In contrast, Lisp freely allows mixing of pure functions and state changing functions. In Lisp it is not safe to use the substitution model without inspecting all functions for side effects.
Leave a Reply