Collections
Keel provides several built-in collection types for grouping and organizing data.
Lists
Lists are ordered, homogeneous collections:
let numbers = [1, 2, 3, 4, 5]
let names = ["Alice", "Bob", "Charlie"]
let empty: List Int = []
numbers
Try itList types can be written as List Int or with bracket syntax [Int]:
let xs: [Int] = [1, 2, 3]
let ys: List Int = [4, 5, 6] -- equivalent
Multi-line Lists
Lists support Elm-style multi-line syntax with leading commas. This is especially useful for long lists or when passing data directly to functions:
let users =
[ { name = "Alice", age = 30 }
, { name = "Bob", age = 25 }
, { name = "Charlie", age = 35 }
]
-- Pass directly to functions without intermediate bindings
DataFrame.fromRecords
[ { name = "Alice", score = 95 }
, { name = "Bob", score = 87 }
, { name = "Charlie", score = 92 }
]
Multi-line literals must be indented, with commas aligned at the same indentation level.
List Operations
-- Prepend (cons)
let consed = 1 :: [2, 3] -- [1, 2, 3]
let built: List Int = 1 :: 2 :: 3 :: [] -- [1, 2, 3]
-- Concatenation
let joined = [1, 2] ++ [3, 4] -- [1, 2, 3, 4]
joined
Try itList Access
Use bracket notation for index access:
let items = [10, 20, 30]
items[0]
Try itList Pattern Matching
let list = [1, 2, 3]
case list of
[] -> "empty"
[x] -> "single element"
x :: xs -> "has multiple elements"
Try itCommon List Functions
-- Map: transform each element
[1, 2, 3] |> map (|x| x * 2) -- [2, 4, 6]
-- Filter: keep matching elements
[1, 2, 3, 4] |> filter (|x| x > 2) -- [3, 4]
-- Fold: reduce to single value
[1, 2, 3, 4] |> fold (|acc x| acc + x) 0 -- 10
Tuples
Fixed-size, heterogeneous collections:
let pair = (1, "one")
let triple = (True, 42, "hello")
let point = (3.5, 4.2)
pair
Try itAccessing Tuple Elements
Use numeric indices with dot notation:
let pair = (1, "hello")
let first = pair.0 -- 1
let second = pair.1 -- "hello"
first
Try itlet triple = (True, 42, "world")
triple.2
Try itTuple Destructuring
let (x, y) = (10, 20)
x + y -- 30
Try itTuple Patterns
let pair = (3, 0)
case pair of
(0, 0) -> "origin"
(x, 0) -> "on x-axis"
(0, y) -> "on y-axis"
(x, y) -> "somewhere else"
Try itRecords
Named field collections:
let user = { name = "Alice", age = 30, email = "alice@example.com" }
user.name
Try itMulti-line Records
Records also support multi-line syntax with leading commas:
let config =
{ host = "localhost"
, port = 8080
, debug = True
, maxConnections = 100
}
Record Access
Use dot notation for field access:
let user = { name = "Alice", age = 30 }
user.name -- "Alice"
Try itNested record access:
let data = { user = { profile = { name = "Alice" } } }
data.user.profile.name -- "Alice"
Try itRecord Update
Create a new record with some fields changed using the | syntax:
let person = { name = "Alice", age = 30 }
let older = { person | age = 31 }
older.name
Multiple field updates:
let person = { name = "Alice", age = 30 }
let updated = { person | age = 31, name = "Alicia" }
updated.name
Record Patterns
Match record fields:
let person = { name = "Bob", age = 25 }
case person of
{ name, age } -> name
Try itWith rest pattern (..) to ignore remaining fields:
let user = { name = "Carol", age = 35, email = "carol@example.com" }
case user of
{ name, .. } -> "Hello, " ++ name
Try itPartial record patterns without .. are an error — this ensures you explicitly acknowledge ignored fields.
Record Destructuring
let person = { name = "David", age = 40 }
let { name, age } = person
name
Try itChoosing the Right Collection
| Collection | Use When |
|---|---|
| List | Sequential data, pattern matching on head/tail |
| Tuple | Fixed number of mixed-type values |
| Record | Named fields, structured data |
Common Patterns
Building Lists
-- Prepend element
let list1 = 1 :: [2, 3] -- [1, 2, 3]
-- Concatenate lists
let list2 = [1, 2] ++ [3, 4] -- [1, 2, 3, 4]
list2
Try itProcessing Lists
fn length : List a -> Int
fn length list =
case list of
[] -> 0
x :: xs -> 1 + length xs
length [1, 2, 3, 4, 5] -- 5
fn reverse : List a -> List a
fn reverse list =
case list of
[] -> []
x :: xs -> reverse xs ++ [x]
reverse [1, 2, 3] -- [3, 2, 1]
Working with Records
let user = { name = "Eve", age = 28 }
-- Access fields
let userName = user.name
-- Update fields (creates new record)
let updatedUser = { user | age = user.age + 1 }
updatedUser.name
Next Steps
Learn about types to define your own data structures.