Its about time we got some job done in Haskell, eh? Now, one of my favourite programming books as an undergraduate was the Camel Book, “Programming Perl”. It was full of lots of practical examples of Perl code, written well. (And I’m grateful to Larry Wall, Tom Christiansen and Randal Schwartz for writing the book that made programming fun).

So what would it look like if we wrote a Haskell tutorial in this style? Let’s have at it!

Getting started

Like some languages Haskell can be both compiled and interpreted. The most widely used implementation of Haskell currently is GHC, which provides both an optimising native code compiler, and an interactive bytecode interpreter. I’ll be using GHC (or its interactive front end, GHCi, for all code. So grab a copy of GHC now, from your package system, or the GHC home page.

Start up GHCi:

    $ ghci
       ___         ___ _
      / _  /  // __(_)
     / /_// /_/ / /  | |      GHC Interactive, version 6.6, for Haskell 98.
    / /_\/ __  / /___| |
    ____// /_/____/|_|      Type :? for help.

    Loading package base ... linking ... done.

The interpreter now waits for your code fragments. The “Prelude” prompt indicates which library modules are in scope, and in this case, only the basic language module, known as the Prelude.

Now we can start running Haskell code.

    Prelude> "G'day, world!"
    "G'day, world!"

    Prelude> putStrLn "G'day, world!"
    G'day, world!


    main = putStrLn "G'day, world!"


    $ ghc A.hs


    $ ./a.out 
    G'day, world!



    phrase = "G'day, world!"
    main = putStrLn phrase

We don’t have to define what type phrase is, as Haskell uses type inference to infer at compile time the types of all expressions in the program. As “G’day, world!” is a string, so must phrase be a string. There are a bunch of basic types of values to play with. Here’s a small sample:

    answer      = 42
    pi          = 3.141592653589793238462643383279502884197169399375105820974944592
    avocados    = 6.02e23
    pet         = "Lambda"
    sign        = "I love my " ++ pet
    coat        = "It costs $100"
    hence       = "whence"
    thence      = hence
    moles       = 2.5
    x           = moles * avocados
    c           = '#'
    pair        = (2.5, "lambdas")
    list        = [5,6,4,3,1]
    options     = Just "done"
    failed      = Nothing
    void        = ()

One important thing to remember is that Haskell’s variables, like in most functional programming languages, are like variables in mathematics, and are just names for expressions. They’re explicitly not mutable boxes, like in most imperative programming languages. As a result, you never need to worry about initialising a Haskell variable, nor do you need to worry about the current value in a variable: it always has the same value, and can always be replaced with its definition. So the following behaves just like it would in maths:

    answer      = 42
    another     = answer + 1
    more        = another + answer
    main        = print more

That is,

    $ ghc A.hs
    $ ./a.out 


    main = print more 
    main = print (another + answer)
    main = print ((answer + 1) + answer)
    main = print ((answer + 1) + 42)
    main = print ((42 + 1) + 42)
    main = print (43 + 42)
    main = print 85

Having such a simple system for variables allows for a wide range of interesting optimisations, and makes understanding what a program is doing at any point much easier, since you don’t have to worry about what state a variable might currently be in. (Of course, some problems need (theadsafe) mutable boxes, and they’re available as a library for when you need that).






We can play around with this stuff like so:

    $ ghci A.hs

    *Main> home

    *Main> head home

    *Main> tail home

    *Main> last home  

    *Main> home !! 2

    *Main> reverse home

    *Main> map reverse home


    *Main> :m + Data.List

    *Main Data.List> intersperse "#" home

    *Main Data.List> concat (intersperse "#" home)

    *Main Data.List> home \ ["table","stove"]

Finite Maps

Finite maps (or maps) are the lookup tables of purely functional programming. Whenever you’d use some kind of hash in an imperative language, you can replace it with a Map in Haskell. Like hashes, maps can be seen as a table of pairs of keys and values. You can declare a new map:

    import Data.Map

    days = fromList
        [ ("Sun",  "Sunday"     )
        , ("Mon",  "Monday"     )
        , ("Tue",  "Tuesday"    )
        , ("Wed",  "Wednesday"  )
        , ("Thu",  "Thursday"   )
        , ("Fri",  "Friday"     )
        , ("Sat",  "Saturday"   ) ]


    *Main> toList days


    *Main> keys days

    *Main> elems days


    *Main> Data.Map.lookup "Tue" days

Since the name ‘lookup’ is also used by a list function of similar purpose in the Prelude, we use the qualified name here to disambiguate which ‘lookup’ to use.

On failure

But what happens if the key is not found? (Feel free to skip this section if you don’t care about errors yet) lookup will then fail, and how it fails depends on what kind of failure you want. Haskell goes to great lengths to make programming for failure flexible. For example, to fail with an exception:

    *Main> Data.Map.lookup "Thor" days

Which is the same as failing with an IO error. We can specify this specifically with a type annotation, to say “fail with an IO error”:

    *Main> Data.Map.lookup "Thor" days :: IO String


    *Main> Data.Map.lookup "Thors" days :: Maybe String

Maybe you’d just like an empty list:


Finally, you can always provide an explicit default value:

    *Main> findWithDefault "Not found" "Thor" days            
    "Not found"

Failure is entirely under your control!




    Prelude> :t putStr
    putStr :: String -> IO ()


    void putStr(char *);



    *Main> main
    "Tue in long form is Tuesday"

An example

One of the classic programming puzzles for introducing real world problems is the ‘class grade’ problem. You have a text file containing a list of student names and their grades, and you’d like to extract various information and display it. In deference to The Camel Book, we’ll follow this lead, and start with a file “grades”, containing something like this:

    Alonzo 70
    Simon 94
    Henk 79
    Eugenio 69
    Bob 80
    Oleg 77
    Philip 73

Student’s appear multiple times, with entries for each of their subjects Let’s read this file, populate a map with the data, and print some statistical information about the results. First thing to do is import some basic libraries:

    import Data.Char
    import Data.Maybe
    import Data.List
    import Data.Map hiding (map)
    import Text.Printf

And now here’s the entire program, to read the grades file, compute all the averages, and print them:

    main = do
        src <- readFile "grades"
        let pairs   = map (split.words) (lines src)
            grades  = foldr insert empty pairs
        mapM_ (draw grades) (sort (keys grades))

    draw g s = printf "%st%stAverage: %fn" s (show marks) avg

Running it

How do we run this program? There’s lots of ways:

Compile it to native code

    $ ghc -O Grades.hs

    $ ./a.out 
    Alonzo  [70.0,71.0]     Average: 70.5
    Bob     [80.0,88.0]     Average: 84.0
    Eugenio [69.0,98.0]     Average: 83.5
    Henk    [79.0,81.0]     Average: 80.0
    Oleg    [77.0,68.0]     Average: 72.5
    Philip  [73.0,71.0]     Average: 72.0
    Simon   [94.0,83.0]     Average: 88.5

Run it in the bytecode interpreter

    $ runhaskell Grades.hs
    Alonzo  [70.0,71.0]     Average: 70.5
    Bob     [80.0,88.0]     Average: 84.0
    Eugenio [69.0,98.0]     Average: 83.5
    Henk    [79.0,81.0]     Average: 80.0
    Oleg    [77.0,68.0]     Average: 72.5
    Philip  [73.0,71.0]     Average: 72.0
    Simon   [94.0,83.0]     Average: 88.5

Execute it interactively

    $ ghci Grades.hs
    Prelude Main> main
    Alonzo  [70.0,71.0]     Average: 70.5
    Bob     [80.0,88.0]     Average: 84.0
    Eugenio [69.0,98.0]     Average: 83.5
    Henk    [79.0,81.0]     Average: 80.0
    Oleg    [77.0,68.0]     Average: 72.5
    Philip  [73.0,71.0]     Average: 72.0
    Simon   [94.0,83.0]     Average: 88.5

Make the script executable


    #!/usr/bin/env runhaskell

And then set the script executable:

    $ chmod +x Grades.hs

    $ ./Grades.hs       
    Alonzo  [70.0,71.0]     Average: 70.5
    Bob     [80.0,88.0]     Average: 84.0
    Eugenio [69.0,98.0]     Average: 83.5
    Henk    [79.0,81.0]     Average: 80.0
    Oleg    [77.0,68.0]     Average: 72.5
    Philip  [73.0,71.0]     Average: 72.0
    Simon   [94.0,83.0]     Average: 88.5

Next week: more IO!