A Haskell tutorial

Abstract

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. 

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.

4.2. 

4.3. 

[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

http://en.wikibooks.org/wiki/Haskell/Laziness

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] 

Either



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]

       [1..2]

       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)))



http://www.haskell.org/arrows/index.html