Learning Haskell - Lists

Published:
Tags:

In Haskell (and functional programming in general), the most important data structure are Lists. One of the reasons is that lists are inherently recursive.

Read the other posts of this serie:

Anatomy of a List

A list can be an element followed by another list or an empty list. The ability of taking apart and building lists are fundamental tools for many functional programming techniques.

The main pieces of a list are the head, the tail and its end represented by []. As you can guess, the head is the first element of the list:

head [10, 20, 100]
-- 10

head [[2, 4], [6, 8]]
-- [2, 4]

And the tail, is just the rest of the list:

tail [10, 20, 100]
-- [20, 100]

tail [[2, 4], [6, 8]]
-- [6, 8]

The tail of lists with just one element is [] or the end of it. A empty list is different from other lists because it has nor head nor tail. If you try to call head or tail in an empty list you will receive an error.

tail [10]
-- []

head []
-- *** Exception: Prelude.head: empty list

tail []
-- *** Exception: Prelude.head: empty list

Creating lists

To create a list in Haskell, you need a function and the infix operator : which is called cons, which is a abbreviation for “construct”. So, to create a list:

1 : []
-- [1]

1 : [2, 3]
-- [1, 2, 3]

1 : 2 : [3, 4]
-- [1, 2, 3, 4]

Please, note that in Haskell, every element in the list must be the same type.

2 : 3 : ['a', 'b']
-- Error!

To combine lists, you can use the ++ operator, the same way you’d use to combine (concatenate) strings, which are lists as well.

[2, 4] ++ [6, 8]
-- [2, 4, 6, 8]

Ranges of data

In Haskell, we can generate ranges of data by using the .. operator inside of a new list.

[1 .. 10]
-- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

[0, 2 .. 10]
-- [0, 2, 4, 6, 8, 10]

Built-in functions

Given lists’ importance, Haskell’s standard library module (Prelude) provides us a wide range of useful functions to use with lists, let’s look at some of the most common ones:

▪️ !! operator

The !! operator provides an easy way to access a particular element in a list by its index. It takes a list and a number and return the element at that location in the list.

[2, 4, 6] !! 2
-- 6

"blackbriar" !! 4
-- k

Any infix operator (an operator placed between two values such as +) can also be used as a prefix function by wrapping it in parenthesis

(!!) [2, 4, 6] 2
-- 6

If you try to access some index that it doesn’t exist in the list, you will receive an error:

[2, 4, 6] !! 3
-- *** Exception: Prelude.!!: index too large

▪️ length

Well, needless to say, it returns the length of the given list:

length [2, 4, 6, 8]
-- 4

length [1 .. 8]
-- 8

▪️ reverse

Again, as the title implies…

reverse [2, 4, 6]
-- [6, 4, 2]

You can use reverse to create a palindrome function:

isPalindrome x = x == reverse x

isPalindrome "spy"
-- False

isPalindrome "ana"
-- True

isPalindrome [1, 2, 1]
-- True

▪️ elem

The elem function takes a value and a list and checks if the given value is in the list or not:

elem 10 [2, 3, 5, 9, 10]
-- True

elem 15 [2, 3, 5, 9, 10]
-- False

elem 'l' "lucas"
-- True

▪️ take

Returns the first n elements in the list, where n is the value provided to the function:

take 10 [1 .. 100]
-- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

take 2 "awesome"
-- aw

If you ask for a value higher than the value list has, it will return what i can with no errors.

take 20 [0, 2]
-- [0, 2]

▪️ drop

The drop function removes the first n elements of the list:

drop 3 [2, 4, 5, 6, 7, 9]
-- [6, 7, 9]

drop 6 "hello haskell"
-- "haskell"

▪️ zip

The zip function allows us to combine two lists into tuple pairs:

zip [0, 2, 4] [6, 8, 10]
-- [(0,6), (2,8), (4,10)]

zip ['a' .. 'z'] [1 ..]
-- [('a',1), ('b',2), ('c',3), ('d',4), ('e',5), ('f',6), ('g',7), ('h',8), ('i',9), ('j',10), ('k',11), ('l',12), ('m',13), ('n',14), ('o',15), ('p',16), ('q',17), ('r',18), ('s',19), ('t',20), ('u',21), ('v',22), ('w',23), ('x',24), ('y',25), ('z',26)]

That’s all!