Lists and Transformations

In this chapter you will learn:

  • How to create and deconstruct lists
  • The core list operations: map, filter, foldl, foldr
  • Other useful list functions from the prelude
  • How to combine list operations into pipelines

Creating Lists

Lists are written with square brackets and commas:

numbers: [1, 2, 3, 4, 5]
strings: ["hello", "world"]
empty: []
nested: [[1, 2], [3, 4]]

Basic List Operations

head and tail

head returns the first element; tail returns everything after it:

eu -e '[10, 20, 30] head'
10
eu -e '[10, 20, 30] tail'
- 20
- 30

Use head-or to provide a default for empty lists:

eu -e '[] head-or(0)'
0

first and second

first is an alias for head. second returns the second element:

eu -e '[:a, :b, :c] second'
b

cons

cons prepends an element to a list:

eu -e 'cons(0, [1, 2, 3])'
- 0
- 1
- 2
- 3

nil?

Test whether a list is empty:

eu -e '[] nil?'
true

count

Count the elements:

eu -e '[10, 20, 30] count'
3

Transforming Lists

map

Apply a function to every element:

eu -e '[1, 2, 3] map(inc)'
- 2
- 3
- 4
eu -e '[1, 2, 3] map(* 10)'
- 10
- 20
- 30

filter

Keep only elements satisfying a predicate:

eu -e '[1, 2, 3, 4, 5, 6] filter(> 3)'
- 4
- 5
- 6

remove

The opposite of filter -- remove elements satisfying the predicate:

eu -e '[1, 2, 3, 4, 5] remove(> 3)'
- 1
- 2
- 3

Folding

Folds reduce a list to a single value by applying a binary function across all elements.

foldl

Left fold: foldl(op, init, list) applies op from the left:

eu -e 'foldl(+, 0, [1, 2, 3, 4, 5])'
15

foldr

Right fold: foldr(op, init, list) applies op from the right:

eu -e 'foldr(++, [], [[1, 2], [3, 4], [5]])'
- 1
- 2
- 3
- 4
- 5

Slicing

take and drop

eu -e '[1, 2, 3, 4, 5] take(3)'
- 1
- 2
- 3
eu -e '[1, 2, 3, 4, 5] drop(3)'
- 4
- 5

take-while and drop-while

eu -e '[1, 2, 3, 4, 5] take-while(< 4)'
- 1
- 2
- 3

Combining Lists

append and ++

eu -e '[1, 2] ++ [3, 4]'
- 1
- 2
- 3
- 4

concat

Flatten a list of lists:

eu -e 'concat([[1, 2], [3], [4, 5]])'
- 1
- 2
- 3
- 4
- 5

mapcat

Map then flatten (also known as flatMap or concatMap):

eu -e '["ab", "cd"] mapcat(str.letters)'
- a
- b
- c
- d

Checking Lists

all-true? and any-true?

eu -e '[true, true, false] all-true?'
false
eu -e '[true, true, false] any-true?'
true

all and any

Test with a predicate:

eu -e '[2, 4, 6] all(> 0)'
true
eu -e '[1, 2, 3] any(zero?)'
false

Reordering

reverse

eu -e '[:a, :b, :c] reverse'
- c
- b
- a

zip-with

Combine two lists element by element:

eu -e 'zip-with(+, [1, 2, 3], [10, 20, 30])'
- 11
- 22
- 33

zip-with and pair to create blocks

eu -e 'zip-with(pair, [:x, :y, :z], [1, 2, 3]) block'
x: 1
y: 2
z: 3

Infinite Lists

Eucalypt supports lazy evaluation, so you can work with infinite lists:

eu -e 'repeat(:x) take(4)'
- x
- x
- x
- x

Use take to extract a finite portion.

Sorting

qsort

Sort with a comparison function:

eu -e '[5, 3, 1, 4, 2] qsort(<)'
- 1
- 2
- 3
- 4
- 5

sort-nums

A convenience for sorting numbers in ascending order:

eu -e '[30, 10, 20] sort-nums'
- 10
- 20
- 30

Putting It Together

Here is a more complete example combining multiple list operations:

data: [
  { name: "Alice" score: 85 }
  { name: "Bob" score: 92 }
  { name: "Charlie" score: 78 }
  { name: "Diana" score: 95 }
]

top-scorers: data
  filter(.score >= 90)
  map(.name)
data:
- name: Alice
  score: 85
- name: Bob
  score: 92
- name: Charlie
  score: 78
- name: Diana
  score: 95
top-scorers:
- Bob
- Diana

Key Concepts

  • Lists are created with [...] and can be heterogeneous
  • map, filter, and foldl/foldr are the core transformation functions
  • take, drop, reverse, append (++), and concat reshape lists
  • all, any, all-true?, and any-true? test list conditions
  • Lazy evaluation allows working with infinite lists via repeat
  • qsort sorts with a custom comparator; sort-nums sorts numbers