Skip to content
AgileLogics
6 min read

Lists

Prerequisites: Values and basic types

The list is Haskell's most-used data structure. All elements of a list have the same type.

main :: IO ()
main = do
  let numbers = [3, 1, 4, 1, 5] :: [Int]
      names = ["Ada", "Grace", "Alan"]
  print numbers
  print (length numbers, head names, last names)
  print (numbers ++ [9, 2])
  print (0 : numbers)
  print (numbers !! 2)
  print (null numbers, null [])
  print (reverse names)
main.hs:6:26: warning: [GHC-63394] [-Wx-partial]
    In the use of `head'
    (imported from Prelude, but defined in GHC.Internal.List):
    "This is a partial function, it throws an error on empty lists. Use pattern matching, 'Data.List.uncons' or 'Data.Maybe.listToMaybe' instead. Consider refactoring to use "Data.List.NonEmpty"."
  |
6 |   print (length numbers, head names, last names)

  |                          ^^^^

[3,1,4,1,5]
(5,"Ada","Alan")
[3,1,4,1,5,9,2]
[0,3,1,4,1,5]
4
(False,True)
["Alan","Grace","Ada"]
  • ++ joins two lists. x : xs (cons) puts one element in front.
  • !! gets the element at an index, counting from 0 — it's slow on long lists and crashes on a bad index, so it's rarely used.
  • null tests for emptiness. Prefer it to length xs == 0, which walks the whole list.
  • The warning at the top comes from GHC 9.8 and later: head crashes on an empty list, so GHC reminds you to consider pattern matching instead. The program still runs. (last is just as partial, but isn't flagged.)

How lists are built

A list is either empty ([]) or an element consed onto another list. [1, 2, 3] is just nicer syntax for 1 : 2 : 3 : []:

main :: IO ()
main = do
  print (1 : 2 : 3 : [])
  print ([1, 2, 3] == 1 : [2, 3])
  print ('H' : "askell")
[1,2,3]
True
"Haskell"

This matters because lists are singly linked: adding to the front with : is instant, but ++ and !! have to walk the list. Build lists by consing to the front, and reverse at the end if needed.

Strings are lists

String is just another name for [Char], so every list function works on strings:

main :: IO ()
main = do
  print (length "Haskell")
  putStrLn (reverse "stressed")
  print (last "Curry", "abc" ++ "def")
7
desserts
('y',"abcdef")

Lists are immutable

main :: IO ()
main = do
  let original = [1, 2, 3]
      extended = 0 : original
  print original
  print extended
[1,2,3]
[0,1,2,3]

0 : original doesn't change original. The new list shares its tail with the old one — safe, because neither can ever be modified.

Common mistakes

  • Mixing types: [1, "two"] is a type error. Use a tuple or your own data type (Level 2) for mixed data.
  • Using head or last on a list that might be empty — use pattern matching instead (coming up soon).