Collections

Lists and maps are Risor's two collection types. Both support indexing, slicing, and functional methods for iteration. Without loops, these methods are how you process data.

Lists

Ordered, mutable, and heterogeneous. Lists hold values of any type:

let items = [1, 2, 3]
let mixed = ["hello", 42, true, nil]
let empty = []

Indexing

Access elements by zero-based index:

let items = [10, 20, 30, 40, 50]
items[0]     // 10
items[2]     // 30

Negative indices count from the end:

items[-1]    // 50
items[-2]    // 40

Slicing

Slice syntax [start:end] extracts a sub-list. Start is inclusive, end is exclusive:

let items = [10, 20, 30, 40, 50]
items[1:3]   // [20, 30]
items[:2]    // [10, 20]
items[2:]    // [30, 40, 50]

Maps

Maps hold unordered key-value pairs with string keys:

let person = {name: "Alice", age: 30}
let empty = {}

Accessing Values

Values can be accessed with dot notation or bracket notation:

let m = {a: 1, b: 2}
m.a        // 1
m["a"]     // 1

Bracket notation is required when the key is stored in a variable:

let key = "a"
m[key]     // 1

Membership Testing

Use the in operator to check whether a key exists in a map:

"name" in person       // true
"email" in person      // false

Spread Expressions

The spread operator ... expands a collection into its elements.

Spread a list into a new one:

let a = [1, 2]
let b = [...a, 3, 4]             // [1, 2, 3, 4]

Merge maps together:

let merged = {...{a: 1}, ...{b: 2}}   // {a: 1, b: 2}

Spread arguments into a function call:

let args = [1, 2, 3]
fn(...args)

Destructuring

Pull values out of collections and bind them to variables in one step.

Map Destructuring

let {name, age} = {name: "Alice", age: 30}
// name is "Alice", age is 30

Provide default values for missing keys:

let {x = 5} = {}
// x is 5

List Destructuring

let [first, second] = [1, 2]
// first is 1, second is 2

Iterator Protocol

Maps, ranges, and other types produce lazy iterators that implement the Enumerable interface. Values are generated on demand rather than materialized into full collections up front.

Map Iterators

let m = {a: 1, b: 2}
let k = m.keys()                // lazy iterator
list(k)                         // ["a", "b"]
[...m.values()]                 // [1, 2]

Use .entries() to iterate over key-value pairs:

[...m.entries()]               // [[k1, v1], [k2, v2], ...]

Range Iterators

range produces a lazy sequence of integers:

list(range(5))                 // [0, 1, 2, 3, 4]
range(5).each(i => nil)        // iterate through values

Consuming Iterators

Iterators can be consumed in two ways:

  • Call list() to collect all values into a list
  • Use the spread operator [...] to expand into a list literal
let values = list(m.values())
let keys = [...m.keys()]

Functional Iteration

No loops means functional methods are the standard toolkit for processing collections:

let numbers = [1, 2, 3, 4, 5]
 
// Transform each element
numbers.map(x => x * 2)                    // [2, 4, 6, 8, 10]
 
// Keep elements matching a condition
numbers.filter(x => x > 3)                 // [4, 5]
 
// Accumulate a single result
numbers.reduce(0, (acc, x) => acc + x)     // 15
 
// Run a function for each element
let total = 0
numbers.each(x => { total += x })

Combine them with pipes for multi-step transformations that read top to bottom:

let evens = [1, 2, 3, 4, 5].filter(x => x % 2 == 0)
evens.map(x => x * x)

Risor © 2026 Deep Noodle