Notes from a Blog Post by Yann Esposito
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.
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 λ
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.
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
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)
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
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!
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))
(x:xs)
will be converted to a tree where:
x
xs
which are strictly inferior to x
and the right subtree is the tree created from members of the list xs
which are strictly superior to x
.
We didn’t specify show so that we can make our own:-- 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
-- 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...]
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
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.