Containers

Weave has one structured data type: the Container. A Container is an ordered sequence of values where any value can optionally have a symbol key — one structure that serves as a list, map, struct, etc. By keeping our structured data to a single interface, Weave guarantees that every structure is compatible with the operators.

Creating Containers

Container literals use square brackets:

# List-style — just values
numbers = [1, 2, 3]
words = ["hello", "world"]

# Map-style — key:value pairs
user = [name: "Alice", age: 30, role: "engineer"]

# Mixed — bare values and keyed values together
mixed = [1, "hello", active: true]

# Nested containers
team = [
    name: "Backend",
    scores: [95, 87, 92],
    lead: [name: "Bob", level: "senior"]
]

# Empty container
empty = []

There is no distinction between “creating a list” and “creating a map” — it is always a Container.

Accessing Data

By Index

Containers are 0-based and maintain insertion order. Negative indices count from the end. Integer indexing is always by position:

list = [10, 20, 30, 40]
list[0]     # 10
list[-1]    # 40

m = [a: 1, b: 3, c: 5]
m[0]    # 1
m[2]    # 5

By Key

Access values by symbol or string key. Missing keys return null:

user = [name: "Alice", age: 30]
user[:name]     # "Alice"
user["name"]    # "Alice" — equivalent

user[:email]    # null

Dot-Access Syntax

c.foo is equivalent to c[:foo]. Works for any key without whitespace:

user = [name: "Alice", age: 30]
user.name    # "Alice"

# Chaining on nested containers
config = [db: [host: "localhost", port: 5432]]
config.db.host         # "localhost"
config[:db][:host]     # "localhost" — equivalent

# Works with function pointers too
handlers = [greet: ^(name) { puts("Hello, {name}!") }]
handlers.greet("Alice")        # Hello, Alice!
handlers[:greet]("Alice")      # Hello, Alice! — equivalent

Mutating Containers

Assign by key, dot access, or index:

user = [name: "Alice", role: "dev"]

# By key
user[:role] = "lead"

# By dot access
user.role = "lead"

# By index
user[1] = "lead"  # ... this works, but it's not idiomatic.

Append values with <<:

items = [1, 2, 3]
items << 4          # [1, 2, 3, 4]

Augmented assignment works as expected:

stats = [count: 0, total: 100]
stats.count += 1     # [count: 1, total: 100]

You can freely mix — add keys to a keyless container, or append bare values to a keyed one:

list = [1, 2, 3]
list[:label] = "numbers"    # [1, 2, 3, label: "numbers"]

record = [name: "Alice"]
record << 42                # [name: "Alice", 42]

Encapsulated Data Objects

Weave is not an Object-Oriented language, but you can fake some aspects of objects using function pointers and closures:


# Fib container
fib = [ a: 0, b: 1 ]  # initial state

next = ^(f) { 
  c = f[:a] + f[:b]
  f[:a] = f[:b]
  f[:b] = c 
}

# Creates a closure over 'fib' and stores it within fib itself!
fib[:next] = partial(next, fib)

fib.next() # Returns the next Fib number with each call 1, 2, 3, 5, etc...

Iteration

Iterating over a container yields values and ignores keys.

Map with *> applies a function to each value and preserves keys:

scores = [alice: 90, bob: 85, carol: 92]
scores *> ^(s) { s + 10 }    # [alice: 100, bob: 95, carol: 102]

Reduce with &> accumulates values into a single result:

fn sum(val, acc: 0) { acc + val }
scores &> sum    # 267

When you need keys, use the .keys property to iterate over them explicitly:

scores.keys *> ^(k) {
    puts("{k}: {scores[k]}")
}

Weave does not unpack key-value pairs during iteration. This keeps iteration simple: always values, always in insertion order.

Built-in Container Properties and Methods

Containers have a few special built-in properties and methods.

Property What it returns
.keys Container of symbol keys
.len Number of elements
.sort() Sorts values (ascending)
.sort(:desc) Values sorted descending
.to_str() Values concatenated as a string
data = [x: 10, y: 20, z: 5]
data.keys           # [:x, :y, :z]
data.len            # 3
data.sort           # [z: 5, x: 10, y: 20]
data.sort(:desc)    # [y: 20, x: 10, z: 5]

[1, 2, 3].to_str()  # "123"
"hello".len          # 5

Slicing

[start:end] includes start and excludes end. Works on containers and strings:

list = [10, 20, 30, 40, 50]
list[1:3]    # [20, 30]

str = "Hello"
str[0:3]     # "Hel"

Set Operations

Operator Name Behavior
+ Concatenation Combines both, includes duplicates
| Union Combines both, no duplicates
& Intersection Elements in both
!& Symmetric difference Elements in one but not both
- Difference Elements in left but not right
a = [1, 2, 3]
b = [2, 3, 4]

a + b     # [1, 2, 3, 2, 3, 4]
a | b     # [1, 2, 3, 4]
a & b     # [2, 3]
a !& b    # [1, 4]
a - b     # [1]