Lists
Lists in Haskell are homogenous. They can only contain one type of data.
You can concatenate one list to the end of another list using the ++
double plus operator.
[1, 2, 3] ++ [4, 5, 6]
-- [1, 2, 3, 4, 5, 6]
The ++
operator can be slow because it runs through the entirety of the list before appending to the end.
You can concatenate a value to the front of a list using the :
colon operator. This value must match the types held in the list and can only be applied in front of the list.
5:[1, 2, 3, 4]
-- [5, 1, 2, 3, 4]
You can rewrite list creation using the colon operator and indeed, list literals in haskell are syntactic sugar for this sort of operation.
[1, 2, 3] -- Same as...
1:2:3:[]
You can retrieve an element from an list by using the !!
double exclamation (index) operator.
[1, 2, 3, 4] !! 2
-- 3
"Lewis Carroll" !! 6
-- C
Comparison
You can compare lists by using the standard comparison operators <
, <=
. >
, >=
and ==
. This works if the elements being compared can be compared. Lists are compared in lexicographical order with the first elements being compared and then the next and so on until a definitive comparison can be made.
For example if we were comparing [1, 2, 3]
with [1, 4, 0]
using the >
operator, the comparison will stop as soon as something is found that proves or disproves this. So, first 1
and 1
are compared and found to be equal so a call cannot be made, then 2
and 4
are compared and the function returns with False
.
[1, 2, 3] > [1, 4, 0]
-- False
[1, 2, 3] > [1, 2, 0]
-- True
[1, 2, 3] > [0, 2, 100]
-- True
The exception to this is lists of different lengths where if a definitive call cannot be made the list with the shorter length is “less than”.
[1, 2, 3] > [1, 2]
-- True
[1, 2, 3] > [1, 5]
-- False
If you compare lists for equality using the ==
operator, it will only return True
if all elements are the same.
[1, 2, 3] == [1, 2, 3]
-- True
[1, 2, 3] == [1, 2]
-- False
Functions
You can get the front of a list except its last element by using the init
function.
init [1, 2, 3, 4, 5]
-- 5
Ranges
You can create a range that automatically generates a list by using ..
double periods between the start and end of the ranges. This only works for things that can actually be operated on sequentially.
[1..10]
-- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
You can define a step for a range by demonstrating a comma separated step before use the range operator. In the example below, we start with 2,4
to describe a step range of two and then use the ..
operator to fill in the rest of the sequence.
[2,4..20]
-- [2, 4, 8, 10, 12, 14, 16, 18, 20]
[2,5..35]
-- [2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35]
You can create a descending list by demonstrating a negative comma separated step before using the range operator. 10..0
on its own would not work.
[10,9..0]
-- [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Unbounded / Infinite ranges
You can work with unbounded ranges by taking advantage of haskell’s lazy evaluation. For example, if you wanted the first 10 multiples of 2 you could leave the end of the range unbounded and then use the take
function to get a specific number of elements from that range. The unbounded range would then only be evaluated once it is called on and condensed to a specific bounded list.
take 10 [2,4..] --unbounded range
-- [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
There are also functions like cycle
and repeat
that will produce infinite lists of elements that need to be sliced to get a bounded version of them.
You can produce a bounded list of repeated elements by using the replicate
function.
replicate 3 10
-- [10, 10, 10]
List comprehension
Ranges are useful but limited in that they can only produce very limited sequences. You can create more complex lists using list comprehensions.
You can create a list comprehension by using the form [function arg | arg <- range, optional predicate]
.
[x * 2 | x <- [1..10]]
-- [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
[x * 2 | x <- [1..10], x * 2 >= 12]
-- [12, 14, 16, 18, 20]
You can add multiple comma separated predicates to your list comprehension expressions.
[x | x <- [10..20], x /= 13, x /= 15, x /= 19]
-- [10, 11, 12, 14, 16, 17, 18, 20]
You can combine multiple lists by comma separating the ranges that the lists are drawing from. This works like a 2D iterator, traversing the elements of the first list once and the elements of the second list as many times as there are elements in the first.
[(x, y) | x <- [0..5], y <- [0..5]]
-- [(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,2),(1,3),(2,0),(2,1),(2,2),(2,3),(3,0),(3,1),(3,2),(3,3)]
You can use strings in list comprehension just as you would any list of data.
[c | c <- "gOtorETail", c `elem` ['A'..'Z']]
-- "OET"
You can nest list comprehensions.
You can think of list comprehensions as just another version of the map filter pattern. They allow you to take a range of values, apply a mapping to them and filter the results. The example below shows a function that uses explicit map
and filter
functions to double a set of integers and then returns only those that are divisible by 3
.
double3' range = filter predicate (map double range)
where predicate x = x `mod` 3 == 0
double x = x * 2
This can be rewritten as a list comprehension with the predicate
and double
functions defined inline.
double3 range = [ dx | x <- [0..range], let dx = x * 2, dx `mod` 3 == 0]
The parity of list comprehensions and map
filter
functions is noted, however, Haskell resources stress that map
is preferable, especially in cases of a map
of a map
as it is more readable.