Destructuring
Do supports destructuring data in let, bind, and for.
let Destructuring
Destructure arrays and similar sequences by listing multiple names:
By default, the pattern must exhaustively match the entire structure or an
error will result. Use ... to capture surplus items instead. The specified
variable will be bound to an iterator over them.
Specify nothing after ... to simply ignore surplus items:
Destructure dictionaries and similar key/value structures with key patterns:
Mixed positional/key destructuring is also possible, with the semantics depending on the structure. For dictionaries, positional patterns bind incrementing integer keys:
Positional and Key Rests
* captures only surplus positional items, and ** only surplus key items.
Either or both may end a pattern, * first, in place of .... Surplus items
of a kind that neither takes are an error, as without a rest:
let first *others **options = {1, 2, 3, color: "red"}
assert_eq $first 1
assert_eq [...others] [2, 3]
assert_eq {...options} {color: "red"}
* or ** alone discards surplus items entirely.
Non-symbol Keys
:key and key: name match symbol keys. External inputs such as decoded
JSON will typically have string keys. To destructure such data, use a constant
expression instead of a bare key:
import json
let payload = json.decode r|
{"name": "Alice", "age": 30}
let "name": name "age": age = payload
assert_eq $name "Alice"
assert_eq $age 30
bind
bind is similar to let but takes the scrutinee (the value to destructure)
first and provides the destructuring pattern in vertical layout. This is
useful when the pattern is more complex than what you're destructuring.
bind {1, foo: false, 2, bar: nil}
a b
:foo :bar
assert_eq $a 1
assert_eq $b 2
assert_eq $foo false
assert_eq $bar nil
Default Values in bind
bind also permits specifying default values for missing items:
bind []
a = 1
b = 2
assert_eq $a 1
assert_eq $b 2
bind [false]
a = 1
b = 2
assert_eq $a false
assert_eq $b 2
bind {}
:foo = 42
assert_eq $foo 42
bind {foo: nil}
:foo = 42
# nil is a present value, not missing
assert_eq $foo nil
Conditional Destructuring
let and bind after if or while make the destructuring itself the
condition: the bindings are in scope for the branch body when the pattern
matches, and the else branch runs when it does not.
bind takes the same vertical layout as its statement form, with do
introducing the branch body. Because the pattern is vertical, it also supports
default values:
Both forms work with while, in which case the loop ends the first time the
pattern fails to match:
Both forms also work where if appears in
vertical layout, building arrays, dictionaries, or
argument lists:
What Counts as a Match
Only a shape mismatch takes the else branch: too few or too many positional elements, or a missing or unexpected key. Any other error raised while destructuring propagates as usual. In particular, destructuring a value that does not support it at all is an error rather than a silent non-match:
# Branches: [1, 2] unpacks fine, but not into three elements
if let a b c = [1, 2]
echo unreachable
else
echo "wrong arity"
# Raises: an int cannot be destructured at all
if let a b = 42
echo unreachable
else
echo also-unreachable
A default supplies a missing element, so it turns what would otherwise be a mismatch into a match.
Binding a Single Name
A pattern that is a bare identifier binds the scrutinee itself and branches on its truthiness rather than destructuring it:
Destructuring in for
Destructure elements during iteration:
for k v = {name: "Alice", age: 30}
echo "$k: $v"
for index value = [10, 20, 30].pairs()
echo "$index: $value"
for :name :age = [{name: "Alice", age: 30}, {name: "Bob", age: 44}]
echo "$name is $age years old"