Learn Haskell Hard and Fast

Notes from a Blog Post by Yann Esposito

Contents

Purity

Laziness

Types

f :: a -> a -> a
f Age YearOfBirth = Age * YearOfBirth

Here a function f takes in two parameters of type a and returns a value also of type a. Here we are multiplying hence a can be Int, Integer or Float. But also more complext custom types if in that case you have defined what happens when you mlutipy your custom types.

We can also use typeclasses. A typeclass is a set of types. Num contains only the types that behave like numbers so our f type is more acurately defined as:

f :: Num a => a -> a -> a

This means a belongs to the Typeclass Num.

Function Application

No function in Haskell ever has more than 1 argument. Arguments are applied partially. Therefore calling the function f will result in (f 3) 4 calling f with 1 argument partially and then applying to the rest.

We can use \ as lambda functions. The \ symbol kinda looks like λ

Notation

In the following the symbol to states that two expression are equivalent. It is a meta notation, does not exists in Haskell. I will also use to show what the return value of an expression is. Arithmetic

3 + 2 * 6 / 3 ⇔ 3 + ((2 * 6) / 3)

Logic

True || False ⇒ True
True && False ⇒ False
True == False ⇒ False
True /= False ⇒ True (Not Equal to)

Powers

x^n     for n being an integral (Int or Integer)
x**y    for y being any number (Float, Int, ect..)

You can also use Rational Numbers by importing a module Import Data.Ratio

Lists

[]                      ⇔ empty list
[1,2,3]                 ⇔ List of integral
["foo","bar","baz"]     ⇔ List of String
1:[2,3]                 ⇔ [1,2,3], (:) prepend one element
1:2:[]                  ⇔ [1,2]
[1,2] ++ [3,4]          ⇔ [1,2,3,4], (++) concatenate
[1,2,3] ++ ["foo"]      ⇔ ERROR String ≠ Integral
[1..4]                  ⇔ [1,2,3,4]
[1,3..10]               ⇔ [1,3,5,7,9]
[2,3,5,7,11..100]       ⇔ ERROR! I am not so smart!
[10,9..1]               ⇔ [10,9,8,7,6,5,4,3,2,1]

Strings Haskell Strings are a list of Characters [Char]

'a' :: Char
"a" :: [Char]
""  ⇔ []
"ab" ⇔ ['a','b'] ⇔  'a':"b" ⇔ 'a':['b'] ⇔ 'a':'b':[]
"abc" ⇔ "ab"++"c"

In Real code you should use Data.Text or Data.ByteString for a stream of ASCII Characters.

Tuples Elements in a Tuple can have different types:

-- All these tuples are valid
(2,"foo")
(3,'a',[2,3])
((2,"a"),"c",3)

fst (x,y)         x
snd (x,y)         y

Infix vs Prefix Notation In Infix you may define a square function like this:

square :: Num a => a -> a
square x = x^2

In Prefix notation you may do it like so:

square' x = (^) x 2

square'' x = (^2) x

You can also write point free code without explicit parameters. This is called η-reduction

square''' = (^2)

The Parameter is implied.

If statements can look like

absolute :: (Ord a, Num a) => a -> a
absolute x = if x >= 0 then x else -x

or

absolute' x
    | x >= 0 = x
    | otherwise = -x

In the second example indentation is important.

Variable Binding

Let Binding

let
  var1 = expression
  var2 = another expression
  var3 = stuff
in
  result expression

-- for example:

main = let m = n + 2
           n = 37
       in print m

Hard Part (Refactoring a Function)

Recursion is generally perceived as slow in imperative languages. But this is generally not the case in functional programming. Most of the time Haskell will handle recursive functions efficiently.

One way we can define an even sum is like so:

evenSum :: [Integer] -> Integer

evenSum l = accumSum 0 l

accumSum n l = if l == []
                  then n
                  else let x = head l
                           xs = tail l
                       in if even x
                              then accumSum (n+x) xs
                              else accumSum n xs

However this required two functions. We can use where and let to make it neater

evenSum :: Integral a => [a] -> a

evenSum l = accumSum 0 l
    where accumSum n l =
            if l == []
                then n
                else let x = head l
                         xs = tail l
                     in if even x
                            then accumSum (n+x) xs
                            else accumSum n xs

Pattern matching allows us to remove the let ... in structure

evenSum l = accumSum 0 l
    where
        accumSum n [] = n
        accumSum n (x:xs) =
             if even x
                then accumSum (n+x) xs
                else accumSum n xs

converting to a point-free or n-reduced solution.

evenSum :: Integral a => [a] -> a

evenSum = accumSum 0
    where
        accumSum n [] = n
        accumSum n (x:xs) =
             if even x
                then accumSum (n+x) xs
                else accumSum n xs

Higher Order functions make our lives easier!

evenSum l = mysum 0 (filter even l)
    where
      mysum n [] = n
      mysum n (x:xs) = mysum (n+x) xs

Even is bult in so we can filter out the even numbers of l. We can use a fold to make it even neater. foldl’ is a strict type version of fold from Data.List

import Data.List
evenSum l = foldl' mysum 0 (filter even l)
  where mysum acc value = acc + value

Finally writing a neater reduce and n-reduce

import Data.List (foldl')
evenSum :: Integral a => [a] -> a
evenSum = (foldl' (+) 0) . (filter even)

Haskell has a built in sum function so we can:

evenSum :: Integral a => [a] -> a
evenSum = sum . (filter even)

Custom Types

Haskell types are Strong and Static this helps you avoid mistakes. Haskell can infer types of you write a square function where the body is x * x Haskell will assume x can be any Numberal Type. Even a Complex number!!

import Data.Complex

square x = x * x

square (2 :+ 1) -- x :+ y is the notation for (x + iy)

Construction of Types

type Colour = String
type Name = String

showInfos :: Name -> Colour -> String
showInfos name colour = "Name:" ++ name
						++ ", Colour: " ++ colour

however if you swap the name and colour parameters it will still complile as Haskell will treat them identical they are both type string. To fix this we can make a data type:

data Name = NameConstr String
data Colour = ColourConstr String

showInfos :: Name -> Colour -> String
showInfos (NameConstr name) (ColourConstr colour) =
		"Name: " ++ name ++ ", Colour: " ++ colour

This time we cannot swap the name and colour around. Constructors are also functions:

NameConstr  :: String -> Name
ColorConstr :: String -> Color

Data Syntax we can define Data like so:

data TypeName =   ConstructorName  [types]
                | ConstructorName2 [types]
                | ...

The convention is to call those DataTypeName and DataTypeConstructor. We can also use a record syntax:

data DataTypeName = DataConstructor {
                      field1 :: [type of field1]
                    , field2 :: [type of field2]
                    ...
                    , fieldn :: [type of fieldn] }

Use this as follows:

data Complex a = Num a => Complex { real :: a, img :: a}
c = Complex 1.0 2.0
z = Complex { real = 3, img = 4 }
real c  1.0
img z  4

Resursive Types

We can define our own more verbose lists:

data List a = Empty | Cons a (List a)
			  deriving (Show, Read, Eq, Ord)

deriving means haskell will add the specified functions for you!

Binary Trees

import Data.List

data BinTree a = Empty
                 | Node a (BinTree a) (BinTree a)
                              deriving (Eq, Ord)

We can make a function convert a list to a tree:

treeFromList :: (Ord a) => [a] -> BinTree a
treeFromList [] = Empty
treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
                             (treeFromList (filter (>x) xs))
-- declare BinTree a to be an instance of Show
instance (Show a) => Show (BinTree a) where
  -- will start by a '<' before the root
  -- and put a : a begining of line
  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
    where
    -- treeshow pref Tree
    --   shows a tree and starts each line with pref
    -- We don't display the Empty tree
    treeshow pref Empty = ""
    -- Leaf
    treeshow pref (Node x Empty Empty) =
                  (pshow pref x)

    -- Right branch is empty
    treeshow pref (Node x left Empty) =
                  (pshow pref x) ++ "\n" ++
                  (showSon pref "`--" "   " left)

    -- Left branch is empty
    treeshow pref (Node x Empty right) =
                  (pshow pref x) ++ "\n" ++
                  (showSon pref "`--" "   " right)

    -- Tree with left and right children non empty
    treeshow pref (Node x left right) =
                  (pshow pref x) ++ "\n" ++
                  (showSon pref "|--" "|  " left) ++ "\n" ++
                  (showSon pref "`--" "   " right)

    -- shows a tree using some prefixes to make it nice
    showSon pref before next t =
                  pref ++ before ++ treeshow (pref ++ next) t

    -- pshow replaces "\n" by "\n"++pref
    pshow pref x = replace '\n' ("\n"++pref) (show x)

    -- replaces one char by another string
    replace c new string =
      concatMap (change c new) string
      where
          change c new x
              | x == c = new
              | otherwise = x:[] -- "x"

Now the formatting will be a lot nicer:

main = do
  putStrLn "Int binary tree:"
  print $ treeFromList [7,2,4,8,1,3,6,21,12,23]

Gives us:

Int binary tree:
< 7
: |--2
: |  |--1
: |  `--4
: |     |--3
: |     `--6
: `--8
:    `--21
:       |--12
:       `--23

Infinite Structures

-- numbers = [1,2,..]
numbers :: [Integer]
numbers = 0:map (1+) numbers

take' n [] = []
take' 0 l = []
take' n (x:xs) = x:take' (n-1) xs

main = print $ take' 10 numbers

numbers is an infinite structure which is defined by the previous digit being incremented by 1 each time. This will give you all the natural numbers from 0 to infinity. take will give you the first 10 and will only evaluate those hence there is no issue with exceeding the recursion stack. In Haskell you can define infinite lists like so:

[1..]    [1,2,3,4...]
[1,3..]  [1,3,5,7,9,11...]

Side Effects

Basic IO looks like an imperative program:

f :: IO a
f = do
  x <- action1
  action2 x
  y <- action3
  action4 x y

The <- is used to set values.

main = do
	varaible <- action

Side Effects come with Error Detection for which we use Data.Maybe

Without error detection we can write:

toList :: String -> [Integer]
toList input = read ("[" ++ input ++ "]")

main = do
  putStrLn "Enter a list of numbers (separated by comma):"
  input <- getLine
  print $ sum (toList input)

However if the user enters anything weird such as “foo” not comma separated it will break. We can write code using Maybe to catch this.

maybeRead :: Read a => String -> Maybe a
maybeRead s = case reads s of
                  [(x,"")]    -> Just x
                  _           -> Nothing

getListFromString :: String -> Maybe [Integer]
getListFromString str = maybeRead $ "[" ++ str ++ "]"     

main :: IO ()
main = do
  putStrLn "Enter a list of numbers (separated by comma):"
  input <- getLine
  let maybeList = getListFromString input in
      case maybeList of
          Just l  -> print (sum l)
          Nothing -> error "Bad format. Good Bye."

If we get an error this time we get a nicer error message. If we want to loop until we get the right number then we can exctract the getting of the input into a function.

askUser :: IO [Integer]
askUser = do
  putStrLn "Enter a list of numbers (separated by comma):"
  input <- getLine
  let maybeList = getListFromString input in
      case maybeList of
          Just l  -> return l
          Nothing -> askUser

main :: IO ()
main = do
	list <- askUser
	print $ sum list

We typically want to split side effects from pure code. The main function can often look like this:

main w0 =
    let (v1,w1) = action1 w0 in
    let (v2,w2) = action2 v1 w1 in
    let (v3,w3) = action3 v2 w2 in
    action4 v3 w3

This has a lot of temp variables which are just passed to the next function. We can use bind (>>=) to remove the temp varaibles.

main =
  action1 >>= action2 >>= action3 >>= action4

or using the arrow syntax

main = do
  v1 <- action1
  v2 <- action2 v1
  v3 <- action3 v2
  action4 v3

In Haskell the main function is assumed to change the state of the world. Something like main :: World -> World Haskell considers the state of the world as an input to the main function. The actual type of main is closer to:

main :: World -> ((), World)

The () is a unit type. All functions with a side effect have a type of World -> (a, World) where a is the type of the result. For instance getChar has a type of World -> (Char, World)

The Haskell compiler will at each step provide a pointer to a real world id. We can define the type of askUser as:

askUser :: World -> ([Integer], World)

askUser w0 =
    let (_,w1)     = putStrLn "Enter a list of numbers:" in
    let (input,w2) = getLine w1 in
    let (l,w3)     = case getListFromString input of
                      Just l   -> (l,w2)
                      Nothing  -> askUser w2
    in
        (l,w3)

Each line above looks like this: let (y,w') = action x w in each time the output of a function is (answer, newWorldValue) hence each function must have a type similar to:

f :: World -> (a, World)

We can remove temp varaibles by defining a bind:

bind :: IO a
        -> (a -> IO b)
        -> IO b

A pattern from the askUser funtion was:

pattern1 w0 = 
 let (x,w1) = action1 w0 in
 let (y,w2) = action2 x w1 in
 (y,w2)

The types for these are:

action1  :: IO a
action2  :: a -> IO b
pattern1 :: IO b

Which looks very much like the bind function. The goal is to hide the World from the function. If we have this function:

let (line1,w1) = getLine w0 in
let (line2,w2) = getLine w1 in
let ((),w3) = print (line1 ++ line2) in
((),w3)

We can write this using bind:

(res,w3) = (bind getLine (\line1 ->
             (bind getLine (\line2 ->
               print (line1 ++ line2))))) w0

Haskell uses >>= for bind so we can write:

(res,w3) = (getLine >>=
           (\line1 -> getLine >>=
           (\line2 -> print (line1 ++ line2)))) w0

Syntactic Sugar

do
  x <- action1
  y <- action2
  z <- action3
  ...

is replaced by:

action1 >>= (\x ->
action2 >>= (\y ->
action3 >>= (\z ->
...
)))

For lines not using the <- we can use the >> operator

do 
	action1
	action2
	action3

is converted to;

action1 >>
action2 >>
action3

Finally we can traslate the askUser function

askUser :: IO [Integer]
askUser = do
  putStrLn "Enter a list of numbers (separated by commas):"
  input <- getLine
  let maybeList = getListFromString input in
      case maybeList of
          Just l  -> return l
          Nothing -> askUser

main :: IO ()
main = do
  list <- askUser
  print $ sum list

Is translated into:

import Data.Maybe

maybeRead :: Read a => String -> Maybe a
maybeRead s = case reads s of
                  [(x,"")]    -> Just x
                  _           -> Nothing

getListFromString :: String -> Maybe [Integer]
getListFromString str = maybeRead $ "[" ++ str ++ "]"

askUser :: IO [Integer]
askUser = 
    putStrLn "Enter a list of numbers (sep. by commas):" >>
    getLine >>= \input ->
    let maybeList = getListFromString input in
      case maybeList of
        Just l -> return l
        Nothing -> askUser

main :: IO ()
main = askUser >>=
  \list -> print $ sum list

Monads

IO is a monad. This means it has access to syntactic sugar using do notation. Monads are about sequencing not always effects. Monads implement bind and return Maybe is a Monad. There is also a list monad which is useful sometimes.