Haskell
Haskell is a purely functional programming language.
Features of haskell include
- All variables are constants - their values cannot be changed
- Functions have no side effects
- Referential transparency - a function always returns the same value(s), when called with the same argument(s)
-
Lazy evaluation - defers actually computing results as long as possible
- Statically Typed - Compiler knows the datatype of each piece of code
- Type Inference - The datatype is determined by the value stored in a variable
Index
- Start
- Function Calls
- Function Definition
- Lists Introduction
- Range
- List Comprehension
- Tuples
- Data Types
- Function Syntax
- Recursion
- Higher Order Functions
- User-Defined Data Types
1. Start
Open GHC’s interactive mode
$ghci
ghci> 2 + 3
5
ghci> not (True && False)
True
ghci> 5 /= 5 -- Not equal
False
2. Function Calls
Type | Call |
---|---|
Infix Function | <operand> <operator> <operand> |
Prefix Function | <operator> <operand> <operand> |
ghci> 5 * 2
10
ghci> succ 8
9
ghci> min 4 5
4
Function application has the highest precedence in Haskell
ghci> succ 9 * 10
100
ghci> succ (9 * 10)
91
Any prefix function taking 2 arguments can be converted to an infix by using `
ghci> div 5 2
2
ghci> 5 `div` 2
2
3. Function Definition
Create a file with the extension hs
touch filename.hs
We can define a function using
doubleMe x = x + x
Often, we include type signatures along with the function statement
doubleMe :: Integer -> Integer
doubleMe x = x + x
Open ghci and load the file
ghci> :l filename.hs
Call the function
ghci> doubleMe 3
6
If the file is updated we can reload the current file using :r
-- `If` in haskell must always return something
doubleSmallNo x = if x > 100
then x
else x * 2
We use a '
to denote a strict version of a fuction by appending it to the end of the function name (since it has no special meaning).
hello' = "Hello, World!"
4. Lists Introduction
Lists are homogeneous data structures.
-- In ghci, we use 'let' to denote a name
ghci> let nums = [1, 2, 3]
ghci> nums
[1,2,3]
We can concatenate lists using ++
l = [1, 2, 3] ++ [4, 5]
s = "hello" + " " + "world" -- Strings are just lists of characters
The cons
operator (:
) is used to add something to the beginning of a list
ghci> 1:[2,3]
[1,2,3]
We can get an element at an index using !!
operator
-- Index starts at 0
ghci> "hello, world" !! 5
','
Nested lists are also possible. The lists can be of different lengths, but can only be of the same type.
We can compare lists using ==
, <
, >
etc. which compare the elements in lexicographical order.
We have functions head
, tail
, init
, last
which returns the first element, everything except the first element, everything except the last element and the last element respectively.
ghci> head [1,2,3]
1
ghci> tail [1,2,3]
[2,3]
ghci> init [1,2,3]
[1,2]
ghci> last [1,2,3]
3
length
returns the length of a list
ghci> length [1,2,3]
3
null
returns True if a list is empty and False otherwise
ghci> null []
True
reverse
does what it says
ghci> reverse [1,2,3]
[3,2,1]
take
extracts a specified number of elements from the front, meanwhile drop
drops it
ghci> take 3 [1,2,3,4,5]
[1,2,3]
ghci> take 3 [1,2] -- If the length is smaller, it returns the entire list
[1,2]
ghci> drop 2 [1,2,3,4]
[3,4]
ghci> drop 10 [1,2,3,4] -- If the length is smaller, it returns an empty list
[]
maximum
and minimum
are also functions in lists
ghci> maximum [1,2,3]
3
ghci> minimum [1,2,3]
1
We can sum up a list using sum
, and product
returns their product
ghci> sum [1, 2, 3, 4]
10
ghci> product [1, 2, 3, 4]
24
5. Range
Ranges are used to make lists composed of elements that can be enumerated.
ghci> [1..10]
[1,2,3,4,5,6,7,8,9,10]
ghci> ['a'..'z']
"abcdefghijklmnopqrstuvwxyz"
ghci> [2,4..10] -- [first,second..limit]
[2,4,6,8,10]
We can define an infinite list using [first,last..]
ghci> sum (take 100 [2,4..]) -- Sum of first 100 even numbers
10100
cycle
generates an infinite repetition of a list, meanwhile repeat
generates an infinite repetition of an element
ghci> take 20 (cycle "abc")
"abcabcabcabcabcabcab"
ghci> take 20 (repeat 10)
[10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10]
replicate
is a similar function that works like
ghci> replicate 3 20
[20,20,20]
6. List Comprehension
ghci> [2 * x | x <- [1..10]]
[2,4,6,8,10,12,14,16,18,20]
In the above example, we say that we draw our elements from the list [1..10]
, and we bind each of those elements to x
. 2 * x
represents how we want the elements that we have drawn to be reflected in the resulting list.
We can easily add a condition / predicate to our comprehension.
ghci> [2 * x | x <- [1..10], x `mod` 4 == 0]
[8,16]
The process of weeding out elements from a list using predicates is called filtering.
ghci> boomBangs xs = [ if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x]
ghci> boomBangs [5..20]
["BOOM!","BOOM!","BOOM!","BANG!","BANG!","BANG!","BANG!","BANG!"]
ghci> [x+y | x <- [1,2,3], y <- [10,100,1000]] -- Multiple lists
[11,101,1001,12,102,1002,13,103,1003]
ghci> let onlyCapital xs = [ c | c <- xs, c `elem` ['A'..'Z']]
ghci> onlyCapital "heLLO, worLd"
"LLOL"
7. Tuples
Tuples are used to store several heterogeneous elements as a single value.
ghci> (1, 3)
(1,3)
ghci> (3, 'a', "hello")
(3,'a',"hello")
A tuple of size 2 (pair) and a tuple of size 3 (triple) are treated as two distinct types, and they cannot be mixed. Also, tuples with elements having different datatypes are also treated as different.
A tuple must atleast have 2 elements.
Functions on pairs include fst
and snd
ghci> fst (1,2)
1
ghci> snd (1,2)
2
zip
takes two lists, produces a list of pairs
ghci> zip [1,2,3,4,5] [5,5,5,5,5]
[(1,5),(2,5),(3,5),(4,5),(5,5)]
8. Data Types
To check the type of a variable, use :t
ghci> :t 'a'
'a' :: Char
-- '::' is read as "has type of"
ghci> :t (True, '5')
(True, '5') :: (Bool, Char)
-- Type of a function
ghci> :t onlyCapital
onlyCapital :: [Char] -> [Char]
Data Type | Description | Example | ||
---|---|---|---|---|
Int |
Integral numbers, fixed precision | 123 |
||
Integer |
Integral numbers, arbitrary precision | 123456789101112131415 |
||
Float |
Single precision floating point numbers | 123.45 |
||
Double |
Double precision floating point numbers | 123.4567891011 |
||
Bool |
Boolean values | True , False |
||
Char |
Single characters | 'a' |
||
String |
List of characters | "Hello" |
||
Tuple |
Fixed size collection of values, can be of different types | (True, 'a') |
||
List |
Variable size collection of values, must be of same type | [1, 2, 3, 4, 5] |
||
<!– | Maybe |
Optional values, can be Nothing or Just something |
Just "Hello" , Nothing |
–> |
<!– | Either |
Represents a value that can be one of two types | Left "Error" , Right 123 |
–> |
<!– | IO |
Represents a computation which performs I/O and returns a value of type a | getLine , putStrLn "Hello" |
–> |
-- `a` is a **type variable**, which means it can be of any type
ghci> :t head
head :: [a] -> a
-- Type Variables are given names a,b,c...
ghci> :t fst
fst :: (a,b) -> a
Functions that use type variables are called polymorphic functions.
Type Class
Type class is an interface that defines some behaviour. If a type is an instance of a type class, then it supports and implements the behaviour the type class describes.
Equality function takes any two values that are of the same type and returns a bool.
-- Eq type class provides an interface for testing equality
-- Almost all types are an instance of Eq
ghci> :t (==)
(==) :: Eq a => a -> a -> Bool
=>
is the ‘Class Constraint’
-- Ord is a type class for types whose values can be put in some order
ghci> :t (>)
(>) :: Ord a => a -> a -> Bool
-- Values of type class 'Show' can be represented as strings
ghci> :t show
show :: Show a => a -> String
ghci> show 3
"3"
-- 'Read' takes a string and returns an object of type 'read' (opposite of show)
ghci> :t read
read :: Read a => String -> a
ghci> read "True" || False
True
ghci> read "5" :: Float
5.0
-- 'Enum' is for sequentially ordered types; that can be enumerated
ghci> :t succ
succ :: Enum a => a -> a
-- Instances of 'Bounded' have an upper and lower bound
ghci> minBound :: Int
-9223372036854775808
ghci> maxBound :: Char
'\1114111'
-- 'Num' is for numeric
ghci> :t 20
20 :: Num a => a
-- sin, cos, sqrt etc. return 'Floating' type
-- 'Integral' includes only whole numbers
9. Function Syntax
Pattern matching is used to specify patterns to which some data should conform and to deconstruct the data according to those patterns.
lucky :: Int -> String
lucky 7 = "You've won!"
lucky x = "Better luck next time!"
ghci> lucky 4
"Better luck next time!"
ghci> lucky 7
"You've won!"
Guards are used when some property of passed values is satisfied.
greatest x y
| x > y = x
| otherwise = y
where
keyword is used to store the results of intermediate computation results to avoid repetition.
parity n
| x == 0 = "Even"
| x == 1 = "Odd"
where x = n mod 2
let
allows us to bind to variables anywhere in the function, unlike where
which can only be used at the end.
let
is an expression (has a value), meanwhile while is just a binding.
let <bindings> in <expression>
circle r =
let area = pi * r ^ 2
perimeter = 2 * pi * r
in (area, perimeter) -- return value
case
expressions allow us to execute blocks of code for specific values of a variable.
case expression of pattern -> result
pattern -> result
pattern -> result
...
head' xs = case xs of [] -> error "No head for empty lists!"
(x:_) -> x
10. Recursion
-
maximum
max' :: (Ord a) => [a] -> a -- Takes any instance of Ord typeclass -> with ordering max' [] = error "Invalid operation!" max' [x] = x max' (x:xs) = max x (max' xs)
-
replicate
-- replicate 3 's' = "sss" replicate' :: Int -> a -> [a] replicate' n x | n <= 0 = [] | otherwise = x: replicate' (n - 1) x
-
take
take' :: (Num i, Ord i) => i -> [a] -> [a] take' n _ | n <= 0 = [] take' _ [] = [] take' n (x:xs) = x : take' (n-1) xs
-
reverse
reverse' :: [a] -> [a] reverse' [] = [] reverse' (x:xs) = reverse' xs ++ [x]
-
repeat
-- Returns an infinite list repeat' :: a -> [a] repeat' x = x:repeat' x
-
zip
zip' :: [a] -> [b] -> [(a,b)] zip' _ [] = [] zip' [] _ = [] zip' (x:xs) (y:ys) = (x,y):zip' xs ys
-
elem
-- Checks whether an element is in a list elem' :: (Eq a) => a -> [a] -> Bool elem' a [] = False elem' a (x:xs) | a == x = True | otherwise = a `elem'` xs
-
quicksort
Algorithm:
function quicksort(list) if list is empty return empty list select a pivot element (first one here) create two lists, 'lesser' and 'bigger' for each element in list excluding the pivot if element is less than pivot add element to 'lesser' list else add element to 'greater' list return concatenation of (quicksort of 'lesser', pivot, quicksort of 'greater')
Code:
quicksort :: (Ord a) => [a] -> [a] quicksort [] = [] quicksort (x:xs) = let smaller = [a | a <- xs, a <= x] bigger = [a | a <- xs, a > x] in quicksort smaller ++ [x] ++ quicksort bigger
11. Higher Order Functions
A function which takes functions as arguments or returns functions as return values are called higher order functions.
-- Applies any function twice
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)
-- Takes a function and two lists as parameters
-- Joins the lists by applying the function between corresponding elements
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' _ [] _ = []
zipWith' _ _ [] = []
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys
-
Map: takes a function and a list, and applies that function to every element in the list, producing a new list.
map :: (a -> b) -> [a] -> [b] map _ [] = [] map f (x:xs) = f x : map f xs
-
Filter: takes a predicate and a list, and returns the list of elements that satisfy that predicate.
filter :: (a -> Bool) -> [a] -> [a] filter _ [] = [] filter p (x:xs) | p x = x : filter p xs | otherwise = filter p xs
A predicate is a function that returns a Boolean value.
ghci> let listOfFuns = map (*) [0..] ghci> (listOfFuns !! 4) 5 20
-
List Comprehension: a way to filter, transform and combine lists. List comprehensions combine map and filter functions.
[ expression | pattern <- list, condition ]
ghci> [x*2 | x <- [1..10]] [2,4,6,8,10,12,14,16,18,20] -- Draw our elements from [1..10] -- Bind each element to x -- x*2 is the output -- Output specifies how the drawn elements are to be reflected in the resulting list. ~ Map ghci> [x*2 | x <- [1..10], x*2 >= 12] [12,14,16,18,20] -- Predicates are separated from the rest using a comma. ~ Filtering ghci> let lessThan10 xs = [ if x < 10 then True else False | x <- xs, odd x] ghci> lessThan10 [7..13] [True, True, False, False]
12. User-Defined Data Types
We use the data
keyword to define new types.
data Bool = True | False
↑ ^ ^ ^
type value_constructors
We can read this as, Bool type can have a value of True or False.
Value constructors are actually functions that returns a value of a datatype
ghci> data Shape = Circle Float Float Float | Rectangle Float Float Float Float
ghci> :t Circle
Circle :: Float -> Float -> Float -> Shape
Here, the Circle
value constructor can take 3 floats, meanwhile Rectangle
has four fields.
To print out a User-defined type, we need to add deriving Show
at the end, which makes the type part of the Show type class.
data Shape = Circle Float Float Float | Rectangle Float Float Float Float
deriving Show
Introducing a new type Point
,
data Point = Point Float Float deriving Show
data Shape = Circle Point Float | Rectangle Point Point deriving Show
-- Calculate the area of a Shape
area :: Shape -> Float
area (Circle _ r) = pi * r ^ 2
area (Rectangle (Point x1 y1) (Point x2 y2)) = abs (x2 - x1) * abs (y2 - y1)
We can use Record Syntax to write datatypes, with a name corresponding to each field types.
data Person = Person {
fname :: String,
lname :: String,
age :: Int,
height :: Float,
phone :: String
} deriving Show
ghci> let me = Person "Akshay" "Rajan" 21 180 "9440000000"
ghci> me
Person {fname = "Akshay", lname = "Rajan", age = 21, height = 180.0, phone = "9440000000"}
Type Constructors
A type constructor, like a value constructor can take types as parameters to produce new types.
type_parameter
↓
data Maybe a = Nothing | Just a
↑
type_constructor
In the above example, if we pass a char
as a type parameter, we get a type of Maybe char
.
Recursive data structures can be created using type constructors, where one value of some type contains values of that type, which in turn contains more values of that type and so on.
1. Lists
A list is either an empty list, or an element joined together with a ‘:’ with another list.
data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord)
Cons is another word for ‘:’.
2. Queues
We can implement queue using a list.
data Queue = Queue [Int] deriving Show
empty :: Queue
empty = Queue []
isEmpty :: Queue -> Bool
isEmpty (Queue []) = True
isEmpty _ = False
enqueue :: Queue -> Int -> Queue
enqueue (Queue q) x = Queue (q ++ [x])
dequeue :: Queue -> (Int, Queue)
dequeue (Queue []) = error "Queue empty!"
dequeue (Queue (x:xs)) = (x, Queue xs)
peek :: Queue -> Int
peek (Queue []) = error "Queue empty!"
peek (Queue (x:_)) = x
The most efficient approach is using two lists.
data Queue a = Queue [a] [a] deriving Show
empty :: Queue a
empty = Queue [] []
isEmpty :: Queue a -> Bool
isEmpty (Queue ins outs) = null ins && null outs
enqueue :: a -> Queue a -> Queue a
enqueue x (Queue ins outs) = Queue (x:ins) outs
dequeue :: Queue a -> (a, Queue a)
dequeue (Queue [] []) = error "Queue empty!"
dequeue (Queue ins (x:outs)) = (x, Queue ins outs)
dequeue (Queue ins []) = dequeue (Queue [] (reverse ins))
3. Binary Search Trees
A tree is either an empty tree, or it is an element that contains some value and two other trees.
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving Show
-- Create a tree
singleton :: a -> Tree a
singleton x = Node x EmptyTree EmptyTree
-- Insertion
treeInsert :: (Ord a) => a -> Tree a -> Tree a
treeInsert x EmptyTree = singleton x
treeInsert x (Node a left right)
| x == a = Node x left right
| x < a = Node a (treeInsert x left) right
| x > a = Node a left (treeInsert x right)
-- Check if an element is present
treeElem :: (Ord a) => a -> Tree a -> Bool
treeElem x EmptyTree = False
treeElem x (Node a left right)
| x == a = True
| x < a = treeElem x left
| x > a = treeElem x right
-- Creating a Binary Tree from a list
ghci> let nums = [8,6,4,1,7,3,5]
ghci> let numsTree = foldr treeInsert EmptyTree nums
ghci> numsTree
Node 5
(Node 3
(Node 1 EmptyTree EmptyTree)
(Node 4 EmptyTree EmptyTree)
)
(Node 7
(Node 6 EmptyTree EmptyTree)
(Node 8 EmptyTree EmptyTree)
)