Esc
Start typing to search...

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 it

List 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 it

List Access

Use bracket notation for index access:

let items = [10, 20, 30]

items[0]
Try it

List Pattern Matching

let list = [1, 2, 3]

case list of
    []        -> "empty"
    [x]       -> "single element"
    x :: xs   -> "has multiple elements"
Try it

Common 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 it

Accessing Tuple Elements

Use numeric indices with dot notation:

let pair = (1, "hello")
let first = pair.0    -- 1
let second = pair.1   -- "hello"

first
Try it
let triple = (True, 42, "world")

triple.2
Try it

Tuple Destructuring

let (x, y) = (10, 20)

x + y  -- 30
Try it

Tuple 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 it

Records

Named field collections:

let user = { name = "Alice", age = 30, email = "alice@example.com" }

user.name
Try it

Multi-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 it

Nested record access:

let data = { user = { profile = { name = "Alice" } } }

data.user.profile.name    -- "Alice"
Try it

Record 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 it

With rest pattern (..) to ignore remaining fields:

let user = { name = "Carol", age = 35, email = "carol@example.com" }

case user of
    { name, .. } -> "Hello, " ++ name
Try it

Partial 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 it

Choosing the Right Collection

CollectionUse When
ListSequential data, pattern matching on head/tail
TupleFixed number of mixed-type values
RecordNamed 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 it

Processing 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.