Plaintext
Haskell for OCaml programmers
Raphael ‘kena’ Poss
March 2014
modified: 2022-05-02
slug: haskell-for-ocaml-programmers
category: Programming
tags: haskell, ocaml, functional programming, language com-
parison, analysis, programming languages
This post mirrors OCaml for Haskellers from Edward Z. Yang (2010).
Contents
Prologue 2
Why you should learn Haskell 2
Straightforward equivalences 4
Recursive definitions 6
Program structure 7
Program and effect composition 9
Print debugging 10
Mutable variables 11
Arrays 12
Type classes in a nutshell 12
Rolling your own recipe system 14
Haskell limitations 15
1
External links 16
Searching for more information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Further reads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Copyright and licensing 17
Note
The latest version of this document can be found online at https://dr-knz.net/
haskell-for-ocaml-programmers.html. Alternate formats: Source, PDF.
Prologue
Why write a new post when a clever reader could simply “read Edward’s post backwards”?
It’s about the different audience, really.
My experience is that programmers well-versed in Haskell, or who learn Haskell as first language,
tend to have a strong background in mathematics and logical reasoning. For this audience, the abstract
equivalences between Haskell and OCaml are trivial and do not bear repeating. For them, a post like
Edward’s that mosty focuses on the “limitations” of OCaml compared to Haskell, and provides succinct
high-level descriptions of OCaml’s unique features, is sufficient.
In contrast, an OCaml programmer is often someone who has learned OCaml by iterative widening of
their programming toolbox from other languages, perhaps without particular sensitivity to the aesthetics
of abstraction. I once belonged to such an audience; I have taught programming to it, too.
For practical OCaml programmers, I found that it often works better to approach Haskell from its
operational perspective, rather than the formal/equational approach typically taken in Haskell tutorials.
The merits of Haskell’s powerful abstraction compositions, in this context, are a matter best left to self-
discovery, later in the learning process.
For example, Haskell tutorials often focus early on non-strict evaluation and strong typing. This is an
unfortunate starting angle for a seasoned OCaml programmer who already knows Haskell’s type system
very well (OCaml has mostly the same) and who is likely to care immediately about a clear time and space
cost model for his/her code, an expectation broken at first sight by non-strict evaluation.
Why you should learn Haskell
There are two set of important features found in Haskell and not in OCaml: killer features and acquired
tastes.
Killer features are those that can immediately enable better productivity, without incurring a feeling
of loss from leaving OCaml’s world:
• layout-based code structuring;
2
(* ML: begin/end needed to scope {- HS: indentation disambiguates
nested pattern matches *) nested pattern matches -}
match v1 with case v1 of
| 0 -> begin 0 -> case v2 of
match v2 with 1 -> "hello"
| 1 -> "hello" 2 -> "world
| 2 -> "world"
3 -> "yay"
end
| 3 -> "yay"
• declarations after use;
v = sum 20
where {- no equivalent in OCaml -}
sum n = n * (n - 1) / 2
• readable type interfaces directly next to declarations;
(* in mod.mli *) {- in Mod.hs -}
val f : int -> int -> int f :: Int -> Int -> Int
(* in mod.ml *)
f = (+)
let f = (+)
• operator and function overloading;
(* no ad-hoc polymorphism, {- show and (+) are overloaded -}
(+) vs. (+.) *)
begin do
print_string putStr
(string_of_int (3 + 2)); (show (3 + 2))
print_string putStr
(string_of_float (3.0 +. 0.14));
(show (3.0 + 0.14))
end
• more flexibility on operator and function names;
let (^.) a b = a ^ "." ^ b (^.) a b = a ++ "." ++ b
let v = "hello" ^. "world" v = "hello" ^. "world"
(* only punctuation can serve as dot a b = a ^. b
infix operators *) v2 = "hello" `dot` "world"
(* "." is not a valid prefix *) (.) f g x = f (g x)
• configurable operator associativity and precedence.
3
let (/~) a b = a / b (/~) a b = a / b
let (-~) a b = a - b (-~) a b = a - b
(* all operators starting with the infixr 7 -~ {- higher = tighter -}
same character have the same infixr 6 /~ {- r for right-assoc -}
associativity and precedence *)
v = 12 /~ 3 -~ 1 /~ 2
let v = 12 /~ 3 -~ 1 /~ 2 {- = 12 /~ ((3 -~ 1) /~ 2) -}
(* = (12 /~ 3) -~ (1 /~ 2) *) {- = 12 /~ (2 /~ 2) -}
(* = 4 -~ 0 *) {- = 12 /~ 1 -}
(* = 4 *) {- = 12 -}
“Acquired tastes” are Haskell features that a) can yield a significant productivity improvement in the
long run b) can yield terrible performance and/or unreadable code when used naively and thus c) will
require patience and practice until you start feeling comfortable using them:
• pure arrays;
• function definitions by equations over infinite data structures;
• type classes and custom Functor / Applicative / Monoid instances.
Some examples are given later below.
Straightforward equivalences
Naming of types:
int float char string bool userDefined (* OCaml *)
Int Double Char String Bool UserDefined {- Haskell -}
Operators:
= <> ^ @ @@ +. /. *. +. (* OCaml *)
== /= ++ ++ $ + / * + {- Haskell -}
land lor lxor [la]sl [la]sr lnot (* OCaml *)
.&. .|. xor shiftL shiftR complement {- Haskell -}
Functions:
let f x y = x + y f x y = x + y
let g = fun x y -> x + y g = \x y -> x + y
let rec fact n = fact n = {- no "rec" needed -}
if n = 1 then 1 if n = 1 then 1
else n * fact (n-1) else n * fact (n-1)
let rec fact' = function {- equational: order matters -}
| 1 -> 1 fact' 1 = 1
| n -> n * fact' (n-1) fact' n = n * fact' (n-1)
Unit:
4
(* val f : unit -> int *) f :: () -> Int
let f () = 3 f () = 3
Pattern match and guards:
match e with case e of
| 0 -> 1 0 -> 1
| n when n > 10 -> 2 n | n > 10 -> 2
| _ -> 3 _ -> 3
Lists:
let rec len = function len [] = 0
| [] -> 0 len (x:xs) = 1 + (len xs)
| x::xs -> 1 + (len xs)
let v = len ([1; 2] @ [2; 1] @ 3::4::[]) v = len ([1, 2] ++ [2, 1] ++ 3:4:[])
(* v = 6 *) {- v = 6 -}
Parametric types:
(* type parameters use an apostrophe *) {- type parameters use small letters -}
(* tuple types defined with "*" *) {- tuple types defined by commas -}
type 'a pair = P of ('a * 'a) data Pair a = P (a, a)
(* concrete parameter {- concrete parameter
before abstract type *) after abstract type -}
type t = int pair type T = Pair Int
Algebraic data types:
(* predefined *) {- predefined -}
type 'a option = data Maybe a =
| None | Nothing
| Some of 'a | Just a
(* custom *) {- custom -}
type 'a tree = data Tree a =
| Leaf of 'a | Leaf a
| Node of 'a tree * 'a tree | Node (Tree a, Tree a)
Generalized algebraic data types:
5
(* available from OCaml 4.x *) {- compile with -XGADTs -}
type _ term = data Term a where
| Int : int -> int term Int :: Int -> Term Int
| Add : (int -> int -> int) term Add :: Term (Int -> Int -> Int)
| App : ('b -> 'a) term * 'b term App :: (Term (b -> a), Term b)
-> 'a term -> Term a
let rec eval : type a. a term -> a = eval :: Term a -> a
function
| Int n -> n eval (Int n) = n
| Add -> (fun x y -> x+y) eval (Add) = (\x y -> x + y)
| App(f,x) -> (eval f) (eval x) eval (App(f,x)) = (eval f) (eval x)
(* two : int *) two :: Int
let two = two =
eval (App (App (Add, Int 1), Int 1)) eval (App (App (Add, Int 1), Int 1))
Text I/O, string representation and interpretation (yay to overloading):
string_of_int string_of_float (* OCaml *)
show show {- Haskell -}
int_of_string float_of_string (* OCaml *)
read read {- Haskell -}
print_char print_string print_endline (* OCaml *)
putChar putStr putStrLn {- Haskell -}
input_line input_char (* OCaml *)
getLine getChar {- Haskell -}
Functional goodies:
(* "@@" is low-priority application *) {- "$" is low-priority application -}
print_endline @@ string_of_int 3 putStrLn $ show 3
(* can be defined in OCaml *) {- built-in in Haskell -}
val flip : ('a -> 'b -> 'c) flip :: (a -> b -> c)
-> 'b -> 'a -> 'c -> b -> a -> c
let flip f = fun x y -> f y x flip f = \x y -> f y x
val (@.) : ('b -> 'c) -> ('a -> 'b) (.) :: (b -> c) -> (a -> b)
-> 'a -> 'c -> a -> c
let (@.) f g = fun x -> f (g x) (.) f g = \x -> f (g x)
Recursive definitions
In OCaml, recursive definitions are expressed with the keyword rec. In Haskell, all definitions are recursive,
including those of variables. This means that one cannot use the same identifier to bind to successive
definitions in the same scope:
6
let f n = f n =
(* this defines a fresh "n" *) {- must use different identifiers -}
let n = n - 1 in let n' = n - 1 in
n n'
Otherwise, the Haskell compiler will generate a circular data definition for n, which is usually not the
intended result!
Program structure
In OCaml, a program is defined by the concatenation of all the statements in the source code; the control
entry point at run-time is the first statement in the top-level module. There is no function with a special
role as entry point. Execution proceeds by evaluating all statements in order.
let f =
print_endline "start";
fun () -> "world"
let p1 () =
print_endline "hello"
let p2 () =
print_endline (f ())
p1 (); p2 ()
(* prints "start", "hello", "world" *)
In Haskell, a “program” must be defined by a constant (not function) with the special name “main”. The
run-time system starts execution by first constructing the data representations of all top-level definitions;
including the definition of the constant “main”, which stores a data structure akin to a “list of statements”,
or “recipe”. At that point, there cannot be any effects performed yet. Then the run-time system continues
execution by reducing the recipe referenced by main, step by step from the head, performing the effects
described by the statements therein.
To build such a “list of statements” or “recipe”, Haskell provides a shorthand syntax using the do
keyword and newline/semicolon separators:
{- no side-effects in top-level definitions -}
f () = "world"
p1 = do {- p1 is a constant recipe -}
putStrLn "hello"
p2 = do {- p2 is another constant recipe -}
putStrLn (f ())
{- entry program must be called "main" -}
{- it is also a constant recipe -}
main = do p1; p2
In OCaml, all statements in source files are executed when the corresponding modules are imported.
In Haskell, source code files provide only definitions; there are no side-effects performed during module
imports.
7
(* in t.ml *) {- in T.hs -}
module T where
let _ = _ = do
print_endline "hello" putStrLn "hello"
(* in main.ml *) {- in main.hs -}
open T import T
let main = main = do
print_endline "world" putStrLn "world"
(* prints "hello", then "world" *) {- prints only "world", not "hello" -}
This is because only the constant recipe constructed by the “main” definition will be evaluated for
side-effects at run-time, after all top-level definitions are constructed.
In OCaml, a file named "blah.ml" defines a module named "Blah". The module name is derived from the
file name. In Haskell, a module specification in the file specifies the module name:
module Blah where ...
By convention, programmers also name the file containing "module Blah where ..." with filename Blah.hs.
However, it is possible to split a Haskell module definition in multiple source files with different names,
or define multiple Haskell modules in the same source file1 .
Module use:
open Char import Char
(* "lowercase" imported by {- "toLower" imported by
"open" from Char *) "import" from Char -}
let lc = lowercase lc = toLower
(* no explicit import needed {- need "import qualified" to use -}
to use qualified module names *) import qualified Data.List
let lsz = List.length lsz = Data.List.length
It is possible to import a module in Haskell (OCaml’s “open”) without importing all the names it defines.
To include only specific identifiers, place them between parentheses after the module name; to exclude
specific identifiers, use “hiding”:
import Char (toUpper,toLower)
import Char hiding (isAscii)
The set of predefined functions and operators in OCaml is provided by the module Pervasives. In Haskell,
they are provided by module Prelude. It is possible to disable predefined functions using import Prelude hiding
(...).
Contrary to OCaml, it is not possible in Haskell to import/open a module locally to a function. Also,
there are no direct Haskell equivalents for OCaml’s parametric modules (functors). See Haskell limitations
below for suggestions.
1
This opportunity is specified in the Haskell Language Report, the official specification of the Haskell language. However, in
practice, its most popular implementation GHC only properly handles modules where the filename matches the module specifica-
tion therein.
8
Program and effect composition
Composition of pure functions in Haskell is conceptually the same as in OCaml. OCaml predefines "|>"
(reverse pipeline), Haskell predefines "." (composition).
let p x = f (g (h x)) p x = f (g (h x))
let p x = f @@ g @@ h @@ x p x = f $ g $ h $ x
(* "|>" predefined in OCaml *) {- "|>" not predefined in Haskell -}
(|>) x f = f x
let p x = x |> h |> g |> f p x = x |> h |> g |> f
(* "." not predefined in OCaml *) {- "." predefined in Haskell -}
let (@.) f g = fun x -> f (g x)
let p x = (f @. g @. h) x p x = (f . g . h) x
To compose effectful statements with no result, both OCaml and Haskell use semicolons (for Haskell,
inside a do-block). Additionally in Haskell, a newline character is also a valid statement separator in a
do-block.
val f : int -> unit f :: Int -> IO ()
val g : int -> unit g :: Int -> IO ()
let _ = main = do
(* sequence of unit calls *) {- sequence of unit statements -}
f 3 ; f 4 ; f 3 ; f 4 {- \n separates too -}
g 2 g 2
In OCaml, effectful functions that return a value of type t are declared with t as return type.
In Haskell, there are no effectful functions; only functions that return “lists of statements”, or recipes,
as described earlier. This creates a conceptual level of indirection: there are no “functions that perform
an effect and return a value of type t”, but rather “functions that return recipes, so that the subsequent
evaluation of those recipes performs effects and also produces a value of type t”.
A function that returns a recipe is typed with return type “IO t”, which means “a recipe of statements
that computes a value of type t upon its later evaluation”.
To express the production of the computed value inside the do-block, you can use the handy function
return:
(* inc : int -> int (with effects) *) inc :: Int -> IO Int
let inc n = inc n = do
print_endline @@ string_of_int n; putStrLn $ show n
n + 1 return (n + 1)
(* fact : int -> int (with effects) *) fact :: Int -> IO Int
let fact = fact =
let rec factr r n = let factr r n = do
print_endline @@ string_of_int n; putStrLn $ show n
if n = 1 then r if n == 1 then return r
else factr (n * r) (n - 1) else factr (n * r) (n - 1)
in factr 1 in factr 1
In OCaml, you can bind a variable to the return value of an effectful function with “let”, as usual. In
Haskell, another syntax is defined for this purpose using “<-” in a do-block:
9
(* fact : int -> int (with effects) *) fact :: Int -> IO Int
let _ = main = do
let v = fact 3; v <- fact 3
print_endline @@ string_of_int v putStrLn $ show v
(* prints 6 *) {- prints 6 -}
Haskell’s insistence on separating pure functions from “lists of effectful statements that produce values
at run-time” creates a plumbing problem that does not exist in OCaml. Say, for example, you have two
effectful functions like inc and fact above, that both take a value as argument and print something before
producing their result. How to compose them together?
Direct composition works in OCaml (because both return int, which matches their input argument
type), but not in Haskell. Instead in Haskell we must either explicitly bind the return value of the first
effectful definition to a name with “<-”, or use the operators “=<<” and “>>=” (piping of effects):
(* inc : int -> int (with effects) *) inc :: Int -> IO Int
(* fact : int -> int (with effects) *) fact :: Int -> IO Int
let _ = main = do
let p = (fact (inc 2)); tmp <- inc 2; p <- fact tmp;
let q = (fact @@ inc 2); q <- (fact =<< inc 2)
let r = (inc 2 |> fact); r <- (inc 2 >>= fact)
let s = (2 |> inc |> fact) s <- ((return 2) >>= inc >>= fact)
Print debugging
The functional engineer’s nightmare: “what actual argument values is this function really called with at
run-time?”
In OCaml, one can readily interleave “print” statements with the functional code to trace what happens
at run-time. In Haskell, there is an extra issue because “print” statements are only available in the context
of the special recipes with type IO a, for example constructed by the do-block syntax, and these do not fit
type-wise in pure functions with regular non-IO types.
There are two ways to achieve this in Haskell. The “pure and magic” way, and the “impure but obviously
effective” way.
The “pure and magic” way is a function called trace in the library module Debug.Trace. This function has
type String -> a -> a, and will print its first argument during evaluation and return its second argument as
result. From the language’s perspective, this function is pure:
import qualified Debug.Trace
fact n =
let n' = Debug.Trace.trace ("fact: " ++ (show n)) n in
n' * fact (n' - 1)
In some cases, one may want to do other effectful things beside printing text. For example, we may
want to print a timestamp or log output to file. Besides the other functions from Debug.Trace, you can roll
your own tracing utility using the special function unsafePerformIO:
10
let f args ... = let f args... =
unsafePerformIO $ do
print_string ...; putStr ...
(... value computation ...) return (... value computation ...)
unsafePerformIO takes as single argument a recipe as constructed eg. by a do-block. When the enclosing
expression is evaluated at run-time, the recipe is first evaluated for effects and then the final value, given
to “return” within the do-block, becomes the functional value of the enclosing expression. unsafePerformIO has
type IO a -> a.
This is the “impure but obviously effective” way:
(* no import needed *) import System.IO.Unsafe
(unsafePerformIO)
(* add : int -> int -> int *) add :: Int -> Int -> Int
let add a b = add a b =
unsafePerformIO $ do
print_endline @@ "add: " putStrLn $ "add: "
^ (string_of_int a) ++ (show a)
^ ", " ++ ", "
^ (string_of_int b); ++ (show b)
(a + b) return (a + b)
(* v1 : int *) v1 :: Int
let v1 = add 1 1 v1 = add 1 1
let _ = main = do
print_string "hello"; putStr "hello"
print_int v1; putStr . show $ v1
print_int @@ add 2 3 putStr . show $ add 2 3
(* prints add, hello, add *) {- prints hello, add, add -}
(* v1 is evaluated where defined *) {- v1's def is not an evaluation! -}
Despite its name, this construction is quite safe to use and it properly witnesses the evaluation of the
enclosing expression at run-time. It is called “unsafe” because it breaks the usual convention that non-IO
functions are pure. However, in the particular case of print debugging the purity is practically preserved
since the I/O operations do not change the value of the function.
It is bad practice (and strongly frowned upon) to write a Haskell function using unsafePerformIO that is
declared pure via a non-IO type but whose run-time return value is dependent on run-time side effects
other than its arguments.
(unsafePerformIO is a bit of a taboo. Haskell programmers do not talk about it to “outsiders”, in the same
way that OCaml programmers do not talk about Obj.magic. But the engineer’s life would be miserable
without it. Just be careful.)
Mutable variables
OCaml provides the ref type for mutable references to values. In Haskell, the type Data.IORef.IORef does the
same:
11
let _ = main = do
let v = ref "hello"; v <- newIORef "hello"
let s = !v in print_endline s; s <- readIORef v; putStrLn s
v := "world"; writeIORef v "world"
let s = !v in print_endline s s <- readIORef v; putStrLn s
The Haskell library ArrayRef provides some syntactic sugar to simplify the use of mutable references.
Like in OCaml, the behavior of concurrent access to mutable variables by different threads in Haskell
is not properly defined by the language. Just avoid data races on IORef and use Haskell’s MVar type to com-
municate between threads instead.
Arrays
OCaml’s arrays are mutable; although they are less often used, Haskell supports mutable arrays too. Since
in-place array updates are effectful, in Haskell they must be used in the context of recipes, eg. inside
do-blocks:
let _ = main = do
let a = Array.make 10 42; a <- newArray (0,9) 42
let v = a.(0); v <- readArray a 0
a.(2) <- v + v; writeArray a 2 (v + v)
print_int a.(2) putStrLn.show <<= readArray a 2
(* prints 84 *) {- prints 84 -}
The Haskell library ArrayRef provides some syntactic sugar to simplify the manipulation of arrays.
Next to mutable arrays, Haskell provides a lot of different pure array types in different libraries. Unfor-
tunately, using pure arrays is quite cumbersome at first. Only later, when you start to grasp that composi-
tions of functions that operate on arrays are “flattened” before execution (thanks to non-strict evaluation),
does it begin to make sense. On the plus side, you then can start to write powerful reusable abstractions
with arrays. On the down side, your code then becomes unreadable.
Decidely, pure Haskell arrays are an acquired taste, one difficult to share with non-experts.
Type classes in a nutshell
A common task in software engineering is to advertise a set of services using an abstract interface that
hides the internal implementation. For this purpose, OCaml programmers can use objects or parametric
modules.
Since Haskell provides neither objects nor parametric modules, Haskell programmers rely on another
mechanism entirely, called “type classes”. Type classes are nothing like modules but they can help for
encapsulation given the right mindset.
In a nutshell, Haskell type classes express a programming contract over a set of types (hence the name):
that all types in the class, ie. its “instances”, are guaranteed to provide some other related functions.
Moreover, a class can also provide a default implementation for some of its functions.
The usual example is the Eq class, written as follows:
class Eq a where
(==) :: a -> a -> Bool
12
(/=) :: a -> a -> Bool
(/=) x y = not (x == y)
This definition expresses the following: “for all types a in the class Eq a, there exist two operators (==)
and (/=) that accept two arguments of type a and returns a boolean value. Moreover, the class Eq provides
a default implementation of (/=) that uses the actual implementation of (==) by each particular instance.”
Once a class is specified, a programmer can do two things with it: define type instances of the class, or
define functions over instances of the class.
To define an instance of a class, you first define a type, then you define how the type belongs to the
class. For example, one can first define a new type that implements Peano integers:
data Peano = Zero | Succ Peano
Then, express that Peano is a member of Eq :
instance Eq Peano where
(==) Zero Zero = True
(==) (Succ x) (Succ y) = (x == y)
(==) _ _ = False
Once this definition is visible, automatically any expression of the form “x /= y” becomes valid, thanks
to the default implementation of (/=) already provided by Eq.
Given a type class, one can also define functions over any possible instance of that class. For example,
given Eq, one can write a function which for any three values, returns True if at least two are equal:
any2 :: (Eq a) => a -> a -> a -> Bool
any2 x y z = (x == y) || (y == z) || (x == z)
Notice the prefix “(Eq a) =>” in the type signature. This means that “the function any2 is defined over any
type a as long as a is an instance of Eq”.
Type classes can inherit all the operations of another class, before it defines its owns. For example, the
class Ord defines operator (<=) (less than or equal), but for this it requires (==) defined by Eq. This is written
as follows:
class (Eq a) => Ord a where
(<=) :: a -> a -> Bool
{- some other Ord definitions omitted here -}
Type classes can be used directly to implement type-safe “container” data structures over arbitrary
other types. For example, an abstract interface for an associative array over arbitrary keys can be defined
with:
class (Ord k) => Map mt k v where
empty :: mt k v
insert :: k -> v -> mt k v -> mt k v
delete :: k -> mt k v -> mt k v
(!) :: mt k v -> k -> v
This definition creates a new type class called “Map”, whose type instances are all of the form “mt k v”
(this means that all instances of Map must be parametric with two type parameters). It also requires that
the 1st type parameter (k, the key type) of its instances be a member of class Ord. For each instance mt k v,
Map provides four services empty, insert, delete and (!).
Once this definition is visible, it becomes possible to implement one or more concrete associative array
data structures, and make them instances of Map via suitable instance declarations. From the perspective of
third-party modules, the only visible services of these concrete implementations are those provided by the
class.
13
Rolling your own recipe system
The previous sections have emphasized how “lists of statements” or “recipes” must be defined as constant
data structures by the program, and are later consumed by the run-time system during execution, starting
from main, to produce effects.
Once you start to become comfortable with composing these recipes using do-blocks, the <- binding
and the (>>=) and (=<<) operators, a question often arises: is it possible to write one own’s recipe type and
interpreter?
The answer is yes! To do so, you will need to define:
• a parametric type M a that represents a “recipe of statements that eventually produce a value of type
a”;
• a function return :: a -> M a that takes as argument a value, and constructs a recipe intended to pro-
duce that value later;
• a function (>>=) :: M a -> (a -> M b) -> M b that takes as arguments one recipe and a constructor for
another recipe, and “connects” the two recipes together;
• optionally, one or more effectful statements of type M a that can be used in recipes next to return;
• optionally, an interpreter for objects of type M a, that performs its effects in the way you see fit.
Once you have defined the first three properly (see below), the do-block syntax presented earlier is
automatically extended to your new type, inferring the right types from your custom definition of return.
Here are some useful recipe systems already available in Haskell:
• IO a: “recipes” evaluated by the Haskell run-time system for global effects during execution. The
peculiarity of this one is that its implementation is completely hidden from the language; you can’t
redefine IO directly in Haskell. The effectful statements of this recipe system include putStr and getChar,
already presented above.
• Maybe a: “recipes” that eventually provide a value of type a, but where the evaluation stops automat-
ically at the first intermediate statement that returns Nothing. The effectful statements of this recipe
system include fail, which forces Nothing to be generated during the effectful evaluation.
• : “recipes” that eventually provide a value of type a, but where the evaluation keeps a log
Writer l a
of messages generated by the statements. The effectful statements of this recipe system include tell,
which generates a message for the log during effectful evaluation.
• State s a: “recipes” that eventually provides a value of type a, but where each intermediate statement
can modify an internal value of type s (a state). The effectful statements of this recipe system include
get and set, which can access and modify the internal state during effectful evaluation.
About the M-word: There is a word used in the Haskell community which starts with the letter “M”
and causes a great deal of confusion and misunderstandings. I wish to avoid using the M-word entirely in
this document. I believe that using and understanding the M-word is unnecessary to learn how to write
Haskell programs productively.
14
Nevetherless, you should understand that whenever you read something about the M-word, it really
refers to what I explained in this section. When you read “the type T a is a M…”, it really means that “the
type T a describes recipes of statements that produce values of type a during evaluation” and also that “the
type T a is an instance of the type class M..., which provides the services return and (>>=)”.
Likewise, when you read or hear “let’s define a M…”, this simply refers to the act of writing a definition
for a new custom recipe type and two new functions return and (>>=), and then using instance to declare an
instance of the M class with them.
The reason why Haskell programmers eventually end up caring a great deal about the M-word, in
the same way they end up caring about the Applicative, Functor and Monoid classes, is that there are very
good software components that can be defined using only the services of these classes. This means these
software components are greatly reusable, because they apply automatically to all types later declared to
be instances of these classes.
Haskell limitations
Of course, there is also a price to pay. Haskell was not primarily designed to be a functional language
by engineers for engineers. There are three features that OCaml gets “just right” and are unfortunately
completely missing in Haskell:
• parametric modules and module composition;
• named and optional function parameters;
• polymorphic variant types.
Haskell practitioners eventually develop some other conventions (“best practices”) to achieve para-
metric modularity without parametric modules. This usually involves mixing and matching the following
features:
• preprocessing using the C preprocessor (ghc -cpp), which can be used to mimic parametric (but not
composable) modules in the same way as in C;
• record types, placing the module’s type definition in ghost type parameters of a record type, and the
module’s methods in its fields;
• type classes, eg. to replace OCaml’s Map functor and OrderedType parameter, instantiated as IntMap, BoolMap,
etc., by Map and Ord type classes (as suggested above).
Named and optional function parameters, in practice, become less important once the Haskell prac-
tioner knows how to fully leverage higher-order functions and non-strict evaluation. A programming
technique to implement optional function arguments using record types and default record fields was
described by Neil Mitchell in 2008.
Polymorphic variant types ([> `A | `B]) are, unfortunately, without direct equivalent in Haskell. Haskell
is a “closed world language” where type inference can only succeed once all types from all compilation
units are known. However, a language extension from the GHC compiler called Type families can be
abused to provide somewhat similar functionality as OCaml’s polymorphic variants.
15
External links
Searching for more information
• The Haskell wiki.
• The Haskell Wikibook.
• Hoogle: an API search engine, able to search by function name or by type signature.
• The #haskell IRC channel on FreeNode.
• The GHC Manual.
Further reads
• Paul Hudak, John Peterson, Joseph Fasel, and Reuben Thomas. A Gentle Introduction to Haskell,
Version 98. June 2000. Also known as the “official Haskell tutorial”.
• Miran Lipovaca. Learn You a Haskell for Great Good!. April 2011. Also known as “the funkiest way
to learn Haskell”, but a very pleasant read.
• Bryan O’Sullivan, Don Stewart, John Goerzen. Real World Haskell. November 2008. With lots of
practical examples.
• Edward Z. Yang, Resource limits for Haskell. April 2013. Explains how to monitor and control space
usage in Haskell functions, including during non-strict evaluation on parallel computers.
• Robert Harper, Persistence of Memory, April 2011. Promotes the use of persistent (immutable) data
structures, and argues that the common debate about run-time efficiency of mutable vs. immutable
data structures is often misdirected.
• Robert Harper, The Point of Lazyness, April 2011. Argues that non-strict evaluation is desirable even
in eager languages, at least to manage processes and streams elegantly.
• Lennart Augustsson, More points for lazy evaluation, May 2011. Argues that non-strict evaluation
promotes reuse of software components.
• Edward Z. Yang, So you want to add a new concurrency primitive to GHC…, January 2014. Explains
how GHC’s Haskell still has conceptual issues internally with the notion of mutable memory.
• Andreas Voellmy, Junchang Wang, Paul Hudak and Kazuhiko Yamamoto. Mio: A high-performance
multicore IO manager for GHC. September 2013. Explains how GHC implements IO internally, and
how it was recently extended to better support parallel execution.
• Ben Rudiak-Gould, Alan Mycroft and Simon Peyton Jones. Haskell is Not Not ML. 2006. Suggests
that there is an underlying common language behind Haskell and SML, that can run programs writ-
ten in either. Also discussed by Ehud Lamm here.
16
References
• Edward Z. Yang, OCaml for Haskellers, October 2010.
• Xavier Leroy et al., The OCaml system release 4.01, September 2013.
Copyright and licensing
Copyright © 2014-2021, Raphael ‘kena’ Poss. Permission is granted to distribute, reuse and modify this
document according to the terms of the Creative Commons Attribution-ShareAlike 4.0 International Li-
cense. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/.
17