Maps
A map initializer is a semicolon-delimited sequence of zero or
more pairs of field names and associated values of a map, enclosed
in square brackets []
. Maps can also be initialized by evaluating
a subroutine or function that returns a map.
m.1 .= [a::'foo'; b::42; c::[]]
console\log(m.1\a) % Expected output: "foo"
Maps are also used for positional elements by just not giving the value a left-hand side field name.
m.2 .= ['foo';123] % two positional elements
console\log(m.2\2) % Expected output: 123
Positional elements can be accessed with 1-based numerical indexes.
toppings .= ['pepperoni', 'bell peppers']
toppings[1] %= pepperoni
If you want to grab all list elements and leave the map fields behind,
you can access them with the field operator [0]
, the zeroth element
represents only the positional elements of a given map.
You can add fields to a map literal by using the ..
or ::
assignment
operators.
[foo..1;bar::2] % foo is immutable, bar is mutable
We'll go over these operators in greater detail later in this tutorial. For now, just understand that labeling occurs via operators only.
Access
To access a map's fields or positional elements, you use the postcircumfix version of
the map operator []
. You suppy a numerical value to get positional
elements and a text or key value to get at field elements.
some-map[1] % The first element is the 1 index value
some-map[foo]
The first version uses a literal value but the second version will
evaluate the foo
label which could either invoke a subroutine or
acquire direct access to the value in memory.
If you supply a text or key literal, you can get access to the associated field label within the map.
some-map['foo']
some-map["f$('o' ^^ 2)"]
These can also be written with the syntax sugar \
operator.
some-map\1
some-map\foo
some-map\'foo'
some-map\"f$('o' ^^ 2)"
The syntax some-map\foo
, some-map['foo']
and some-map\'foo'
are equivalent.
Computed Field Names
Map initializer syntax also supports computed field names. That
allows you to put an expression in brackets <>
, that will
be computed and used as the property name.
i := 0
a .= [
<"foo$(i := i ++ 1)"> :: i
<"foo$(i := i ++ 1)"> :: i
<"foo$(i := i ++ 1)"> :: i
]
console\log(a\foo1) %= 1
console\log(a\foo2) %= 2
console\log(a\foo3) %= 3
items .= ['A'; 'B'; 'C']
map .= [
<items>..'Hello'
]
console\log(map) %= "A;B;C" .. "Hello"
console\log(map\"A;B;C") %= "Hello"
param .. 'size'
config .. [
<param> :: 12
<"mobile$(param[1]\upper-case)$(param[2|+])"> .. 4
]
console\log(config) %= [size::12; mobileSize..4]
Spread fields
Map literals support the spread operator. It copies the prefixed map's base fields (excluding subfields) from a provided map into a new map. Note that it also spreads the positional elements as well.
m1 .= [foo :: 'bar'; x :: 42]
m2 .= [foo :: 'qux'; y :: 13]
cloned-map .= [&m1] %= [foo::"bar";x::42]
merged-map .= [&m1; &m2] %= [foo::"qux";x::42;y::13]
Submaps
Submaps are "mini-proto-environments" that allow you to specify label references
as a sequence of elements. Each label reference element exists in a kind of
"label limbo" where they await assignment. They can be invoked like functions
where each argument element gets assigned to the label element at that position.
They can also be given to the applicative operator ->
which then becomes your
named parameters for a function. The difference is that when invoking, you are
creating a map with fields. When you supply it to the applicative op, the labels
become local labels within the routine body.
foo ++ bar
warehouse .= <[name; maximum; current::0]>
north .= warehouse('Northside', 2,000.-)
south .= warehouse('South River', 2,500.-)
supply-run .= [load; &warehouse] -> (
remaining .= maximum -- current -- load
remaining << 0 => $not-enough-space(-remaining)
current := current ++ load
)
You can spread them with the &
operator and they will flatten all their
labels as parameters in the main parameter list (itself a submap).
Destructuring
The destructuring syntax is a Rhumb expression that makes it easy to excise just the important data you need from a map and place them into labels in the current scope.
Description
The map literal expression provides an easy way to create ad hoc packages of data.
x .= [1; 2; 3; 4; 5]
The destructuring assignment uses similar syntax, but flipped to the left-hand side of the assignment instead. The map literal now serves to define what elements and fields to destructure from the sourced map.
x .= [1; 2; 3; 4; 5]
[y; z] ^= x
console\log(y) %= 1
console\log(z) %= 2
a .= [b..1; c.. 2]
[.b;.c] ^= a
% equivalent to
b .= a\b; c = [a\c]
Previously Declared Locations
You can even destructure directly into some already existing map.
numbers .= []
map .= [a..1; b..2; c..3]
[a..numbers[1]; b..numbers[2]; c..numbers[>]] ^= map
You can use the append and unshift field ops as well.
Default Value
Each destructured label can have a default value by using the
??
operator. This value will be used only if the value at this
label is empty
.
[1..a??1] ^= [] % a is 1
[.b??2] ^= [b..empty] % a is 2
[.c??3] ^= [c..0] % c is 0
The right-hand side of the default operator is actually auto-referenced as a subroutine so you can do any kind of expression here.
[.b??console\log('hey!')] ^= [b..2]
% nothing is logged because b is not empty
Slurp expressions
You can take arbitrary amounts of elements of positional elements
by using the &
operator. This element
will greedily take all surrounding elements up to the next positional
field given. It also greedily takes all uncaptured fields.
[.a; &others] ^= [a..1;b..2;c..3]
console\log(others) % [b..2;c..3]
[1..first; &others] ^= ['a';'b';'c']
console\log(others) % [1..'b';2..'c']
You can place as many as you want within a destructuring pattern but only the last slurp argument will receive the fields.
[3..prefix; &name; 10..separator; &suffix] .= get-chars(file)
% here, name contains the positional elements from 4 through 9
% suffix contains anything 11 and after
% (and elements 1 and 2 are ignored)
Destructuring with more elements than source.
In a positional destructuring pattern, you can specify elements that do not exist just like you can with nominal destructuring.
foo .= ['one'; 'two']
[red;yellow;green;blue] ^= foo
console\log(red) %= 'one'
console\log(yellow) %= 'two'
console\log(green) %= empty
console\log(blue) %= empty
Swapping variables
Two labels can have their values swapped in one destructuring expression.
a := 1
b := 3
[a; b] ^= [b; a]
console\log(a) %= 3
console\log(b) %= 1
fruits .= ['apples'; 'oranges'; 'bananas']
[fruits\3; fruits\2] ^= [fruits\2; fruits\3]
console\log(fruits) %= [1..'apples'; 2..'bananas'; 3..'oranges';]
Parsing a subroutine map return
You can return a map from a subroutine or function. Destructuring can make working with a map return value more concise.
dangerous .= [n] -> [yes; n ** 1,000,000.-]
[success; reward] ^= dangerous(5)
console\log(success) %= yes
console\log(reward) %= 5,000,000.-
Picking a first positional element
If the map being destructured contains a large number of elements, you can pick an arbitrary point to start your positional elements. In fact, you could just use this syntax for all positional destructuring.
foo .= ['a';'b';'c';'d';'e';'f';'e';'g']
[4..fourth; fifth; sixth] ^= foo
console\log(fourth) %= 'd''
console\log(fifth) %= 'e'
console\log(sixth) %= 'f'
Ignoring a map field or element
You can soft-ignore fields by just not referencing them in a destructuring pattern. However, using the slurp operator will inevitably cause these to be captured. You can explicitly ignore by using the placeholder label.
foo .= [a..'adam'; b..'brian'; c..'charles']
[a.._; &names] ^= foo
console\log(names) %= [b..'brian'; c..'charles']
To ignore positional elements, use the same placeholder but with a positional field.
fruits .= ['apples'; 'oranges'; 'bananas']
[1.._; &non-favorites] ^= fruits
console\log(non-favorites) %= ['oranges'; 'bananas']
The slurp will start from after the most recent positional field has been captured. You can effectively slice into a map by just using destructuring syntax.
name .= ['j'; 'a'; 'c'; 'o'; 'b']
[1.._;∣5.._] ^= name
Taking fields from maps passed as function parameters
Maps passed into function as an argument can be destructured into labels with then can be accessed within the subroutine. As for map assignment, the destructuring syntax allows for the new label to have the same name or a different name from the original field and to assign default values for the case when the original map does not define that field.
Consider this map which contains info about a user.
user .= [
id .. 42
display-name :: 'jdoe'
full-name :: [
first-name :: 'Jane'
last-name :: 'Doe'
]
]
Here we show how to destructure a field of the passed map into a
label with the same name. The parameter value [.id]
indicates that
the id field of the map passed to the function should be destructured
into a label with the same name, which can then be used from within
the subroutine.
user-id .= [[.id]] -> id
console\log(user-id(user)) %= 42
You can define the name of the destructured label. Here we destructure
the field named display-name
and rename it to dname
for use within
the subroutine body.
user-display-name .= [[display-name..dname]] -> dname
console\log(user-display-name(user)) %= 'jdoe'
Inner maps can also be destructured. This example shows the field
full-name\first-name
being destructured into a label called name
.
who-is .= [[.display-name; full-name..[first-name..name]]] ->
"$display-name is $name"
console\log(who-is(user)) %= jdoe is Jane
Setting a function parameter's default value
Default values can be specified using assignment operators, and will be used as label values if a specified field does not exist in the passed map.
Below we show a function where the default size is 'big'
, default
co-ordinates are x::0; y::0
and default radius is 25
.
draw-chart .= [[
:size??'big'
:coords??[x::0; y::0]
:radius??25
]??[]] -> (
console\log(size; coords; radius)
% do some chart drawing
)
draw-chart([coords::[x::18; y::30]; radius::30])
In the function submap for draw-chart
above, the destructured
left-hand side has a default value of an empty map ??[]
.
You could also have written the function without that default.
However, if you leave out that default value, the function will
look for at least one argument to be supplied when invoked, whereas
in its current form, you can call draw-chart
without supplying
any arguments. Otherwise, you nead to at least supply an empty map
literal.
Nested map destructuring
metadata .= [
title :: "Scratchpad"
translations :: [
{
locale :: "en"
localizationTags :: []
lastEdit :: "2023-07-18T21:48:37"
url :: "/en/docs/Tools/Scratchpad"
title :: "Rhumb Environment"
}
]
url :: "/en-US/docs/Tools/Scratchpad"
]
[
title :: english-title
translations :: [
[
title :: locale-title
]
]
] ^= metadata
console\log(english-title)
console\log(locale-title)
<>
iteration and destructuring
people .= [
[
name :: "Mike Smith"
family :: [
mother :: "Jane Smith"
father :: "Harry Smith"
sister :: "Samantha Smith"
]
age :: 35
]
[
name :: "Tom Jones"
family :: [
mother :: "Norah Jones"
father :: "Richard Jones"
brother :: "Howard Jones"
]
age :: 25
]
]
people <> [[name..n; family..[father..f]]] ->
console\log("Name: $n, Father: $f")
% "Name: Mike Smith, Father: Harry Smith"
% "Name: Tom Jones, Father: Richard Jones"
Computed map field names and destructuring
Computed field names, like on map literals, can be used with destructuring.
key .= 'z'
[<key>..foo] ^= [z::'bar']
console\log(foo)
Subfields are looked up when destructuring maps
When desturing a map, if a field is not directly on the base map, it will continue to look into the attached subfields.
my-map .= [
foo :: '123'
@my-sub :: [
bar :: '456'
]
]
[:foo; :bar] ^= my-map
console\log(foo) % '123'
console\log(bar) % '456'
Subfields can also be directly destructured
You can reference a subfield by name to destructure its contents.
my-map .= [
foo :: '123'
@my-sub :: [
bar :: '456'
]
]
[:foo; @my-sub[:bar]] ^= my-map
console\log(foo) % '123'
console\log(bar) % '456'