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