A Haskell tutorial


This week, I have smashed my way into Haskell with abandon, but found little in the way of notes or tutorials concisely explaining the language without wasting my time explaining functional programming paradigms I already know. My own work-in-progress notes presented, to be assembled into something helpful, perhaps.

Table of Contents

I was very drawn in by Haskell this week, and impressed by it on several points: its theory feelms more to me like applied maths than theoretical computing, so its abstractions are very natural and clear to me, and line up well with my understanding of computing; its syntax is rather nice and the meaning they denote again rather nice. It has a large collection of libraries and is widely used, so very quickly I have realised that it is one of the best languages around at the moment.

On the other hand, despite some very good documentation, I have not found many tutorials which teach me what I want: Haskell. Not functional programming (my background is ML), but just Haskell. These are my notes made as I struggled through various highly verbose books and posts, so hopefully represent the minimal account of things which are specific to Haskell or its unusual features.

1. References

  • Start by learning the syntax. Reading too many books will waste a lot of time; Arjan van IJzendoorn’s Tour of the Haskell Syntax and this Haskell Breadcrumb Tutorial are a good place to start. I will present my own summary of the syntax below as well.

  • [LYAH] Learn you a Haskell by Miran Lipovača is an excellent online book, coming out it print this Spring too. It is very chatty, and has a lot of detail, but burried in a format which you will enjoy reading, though also find frustratingly distracting.

  • [RWH] The other highly acclaimed book is Real World Haskell by Bryan O'Sullivan, Don Stewart, and John Goerzen. It is chock-full of great worked examples, but a little light on theoretical details.

  • The Haskell website itself is excellent and offers a huge amount of great content, and the Haskell Wikibook Advanced Track has plenty of useful detail on the theory to help sort out the meaning of the various constructs in the language.

2. Documentation

While learning Haskell, the number one thing to be learning is the official documentation. Haskell code comes in modules, and the standard module which must be loaded in every programme is the Prelude. Read your way through that section, then also Data.List, and so on. (New names to Mathematica users: Gather is group, Flatten is concat, Union is nub, etc. Note also nice goodies like span and concatMap.)

[gems: minBound :: a cool isLetter etc: Unicode 5 (note Char is wide)]

3. Learning GHCi

The ‘standard’ compiler is GHC, which also comes as part of the recently-assembled Haskell Platform. The interractive environment is GHCi, which works pretty much as you would expect. You will have to flick through the documentation and list of commands eventually, but to save you time, these are the important commands (which can all be abbreviated with just their first letter):reload to reload the file and modules with any changes; :info and :type both to use often and early; to avoid confusion note that there are different commands :load and :module. GHCi commands can be split over multiple lines, by wrapping them in :{ and :}. For the fun of it, :! runs shell commands (Just like Vim!—though Vim’s syntax is more flexible :r ! <cmd> and :<range> ! <cmd>). Try this out: :! echo :set prompt '"\ESC[32m%s\ESC[1;34m>\ESC[0;38m "' >> ~/.ghci.

Haskell programs have a special function, main, which GHC picks out and makes the main application. GHCi will not run it by default, but will replicate the execution of a compiled application with :main.

[scope of modules and loading; bindings with IO and it and let;]

There is a good integrated debugger too. Play around with :break and :list and then read that documentation page.


4.1. Variables

We bind names to objects using the let <…> in <…> syntax. Sugar: Declarations made at the top level may omit the let, and the variable is global to the module. Haskell is very similar to any other functional language in terms of its declarative semantics for variables. All objects are functions, as you would expect, accepting differing numbers of arguments, so currying is built into the syntax. A function with no arguments is called a name. The syntax for anonymous functions is using lambda expressions: \x y->x+y.

Related to variable declaration are two important things. Pattern matching is done using the case expression: case xs of {[]->0; (x:xs)->1+length xs}. This expression means pretty much what it looks like. Any type constructor or value can be matched against. The other first-class construction is if <a> then <b> else <c>, where the expression <a> has type Bool, and <b> and <c> have the same type.

Sugar for both of these: pattern matching may be done in function declarations. They may have multiple clauses, which are evaluated in order, and guards may be used as shorthand for conditions. Examples:

x = 15 -- declare a variable
happy x y | x < y = id
          | y < x = \z->z+1
happy x y = \z->if z<0 then 0 else z*z

As a final piece of sugar, we can define ‘as-rules’ which bind the matched pattern as well as the unmatched variable: f x'@(x:xs) = (x',x):f xs.



[NB. Notes incomplete, to be written up. Some surprising things jotted below, but needing substantial reorganisation. Better to post than moulder.]
Notes on infix. All functions have infix precedence; those that are in fact infix are characters only.

read show fromIntegral

Type defaulting: The type variable a appears in no other constraints; All the classes Ci are standard. At least one of the classes Ci is numeric.

error, trace

http://www.haskell.org/haskellwiki/Hoogle Install

learn precedence: (eg : has low prec, so () in fn defs, but no 

with, let

whitespace, {}

removeNonUppercase :: [Char] -> [Char]  only as a block on its own

ghci> [ x | x <- [10..20], x /= 13, x /= 15, x /= 19]  

String = [Char]

Useful operators. on, $ for precedence and to map values over functions, 

reverse' = foldl (flip (:)) [] 
append xs ys = foldr (:) ys xs
myMap f xs = foldr step [] xs
    where step x ys = f x : ys
myFoldl f z xs = foldr step id xs z
    where step x g a = g (f a x)

fn = ceiling . negate . tan . cos . max 50  

ghci> map ($ 3) [(4+), (10*), (^2), sqrt]  

scan and fold

evaluation operator, real TCO w/ foldl', thunks as the implementation
MaybeS a = NothingS | JustS !a mean that JustS *thunk* is not allowed
strictness of functions and evaluation calls
strictness of functions in dealing with their arguments, ie f strict iff f bottom = bottom
Prelude> let f ~(x,y) = 1
Prelude> f undefined
f $! x = x `seq` f x

(application) 10
infixr 9  .
infixr 8  ^, ^^, **
infixl 7  *, /, `quot`, `rem`, `div`, `mod`
infixl 6  +, -
infixr 5  :
infix  4  ==, /=, <, <=, >=, >
infixr 3  &&
infixr 2  ||
infixl 1  >>, >>=
infixr 1  =<<
infixr 0  $, $!, `seq`

infixl 9  !!
infixr 5  ++
infix  4  `elem`, `notElem`

import Data.List; ghci> :m + Data.List Data.Map Data.Set  

data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show)  

data Person = Person { firstName :: String  
                     , lastName :: String  
                     , age :: Int  
                     , height :: Float  
                     , phoneNumber :: String  
                     , flavor :: String  
                     } deriving (Show)   automatically creates functions firstName
ghci> Car {company="Ford", model="Mustang", year=1967}  
polymorphic adt: data Maybe a = Nothing | Just a deriving(Ord) in order declared, Show, Read, Eq
type String = [Char] 

typeclasses. Functor. kinds

putStrLn, print, return, do, <-, IO, mapM_?, getLine/getContents/hGetContents, getArgs/getProgName

import System.Environment   
import System.Directory  
import System.IO  
import Data.List  
dispatch :: [(String, [String] -> IO ())]  
dispatch =  [ ("add", add)  
            , ("view", view)  
            , ("remove", remove)  
main = do  
    (command:args) <- getArgs  
    let (Just action) = lookup command dispatch  
    action args  
add :: [String] -> IO ()  
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n")  
view :: [String] -> IO ()  
view [fileName] = do  
    contents <- readFile fileName  
    let todoTasks = lines contents  
        numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks  
    putStr $ unlines numberedTasks  
remove :: [String] -> IO ()  
remove [fileName, numberString] = do  
    handle <- openFile fileName ReadMode  
    (tempName, tempHandle) <- openTempFile "." "temp"  
    contents <- hGetContents handle  
    let number = read numberString  
        todoTasks = lines contents  
        newTodoItems = delete (todoTasks !! number) todoTasks  
    hPutStr tempHandle $ unlines newTodoItems  
    hClose handle  
    hClose tempHandle  
    removeFile fileName  
    renameFile tempName fileName  

Exceptions: only in IO because evaluation is stateful

a = do x <- [3..4]
       return (x, 42)
is transformed during compilation into:
a = [3..4] >>= (\x -> [1..2] >>= (\_ -> return (x, 42)))

x // y = do
  a <- x  -- Extract the values "inside" x and y, if there are any.
  b <- y
  if b == 0 then Nothing else Just (a / b)
x // y = x >>= (\a -> y >>= (\b -> if b == 0 then Nothing else Just (a / b)))