The language
Collections
Arrays and maps, the two ways Coach groups values together, and how to read and change what is inside them.
Coach supports grouping values together in two ways: arrays and maps. Both are written with square brackets, both can hold any kind of value (including other collections) and both are read with the same dot notation.
Array
An array is a collection of values indexed by whole numbers starting from 0.
It is written as a comma-separated list wrapped in square brackets, and it can
also contain other collections.
Example
let array = [12, 6|kg|, "a string", [4.3, true]]
Access
The access operator is a dot followed by the position of the element we want to read:
let array = [12, 6|kg|, "a string", [4.3, true]]
let first = array.0 // 12
let third = array.2 // "a string"
let nested = (array.3).1 // true
Notice the parentheses in the last line. array.3.1 would not work, because
Coach reads 3.1 as a number; wrapping the first step in parentheses keeps
the two positions apart.
A position can also be computed. Put the expression in parentheses after the dot:
let array = [12, 6|kg|, "a string", [4.3, true]]
let i = 1
let second = array.(i) // 6|kg|
let third = array.(i + 1) // "a string"
Reading a position that holds nothing gives back nil.
Assignment
One can also change an entry of an array, simply by combining access with assignment:
let array = [12, 6|kg|, "a string", [4.3, true]]
array.0 = 13
array.1 = array.1 + 2|kg|
Map
A map is a collection that maps keys to values. Keys are names and values can be any expression. A map is written the same way as an array, but as a comma-separated list of key-value pairs.
Example
let a = 3
let map = [
key1: "string",
key2: 3,
key3: (a > 2 ? 12|kg| : 15|kg|),
key4: [1, 2, 3],
key5: [foo: 4.5]
]
A key that is not a plain name, for example one with a space in it, can be
written as a string: ["two words": 1].
Access
The access notation is a dot followed by the key name:
let map = [key1: "string", key5: [foo: 4.5]]
let s = map.key1 // "string"
let f = map.key5.foo // 4.5
As with arrays, the key can be computed by putting an expression in parentheses. This is how a program picks an entry based on something it only knows while it runs:
let load = [squat: 60|kg|, bench: 45|kg|]
let lift = "bench"
let weight = load.(lift) // 45|kg|
If there is no value at the path, the nil value is returned. That holds for
paths of any length, as long as nothing along the way is a plain value:
let map = [key1: "string", key5: [foo: 4.5]]
let missing = map.key9 // nil
let deeper = map.key9.foo.bar // nil
Assignment
As with the array, one can change an entry of a map by assigning to its path. If any of the intermediate maps is missing, a new map is put in its place:
let map = [key1: "string", key5: [foo: 4.5]]
map.key5.foo = 5
map.key8.bar.baz = 1
/*
map is now:
[
key1: "string",
key5: [foo: 5],
key8: [bar: [baz: 1]]
]
*/
This makes maps a natural place to keep a program’s working state, such as the current load of every lift:
let load = [squat: 60|kg|, bench: 45|kg|]
load.squat = load.squat + 2.5|kg|
Working with collections
A few operators and built-in functions come in handy:
| Expression | What it does | Example | Result |
|---|---|---|---|
x <> array |
whether x is one of the items |
"b" <> ["a", "b"] |
true |
len(c) |
how many entries c holds |
len([4, 5, 6]) |
3 |
has(c, key) |
whether c has a key or position |
has([down: 3], "down") |
true |
a == b |
whether two collections hold the same entries | [1, 2] == [1, 2] |
true |
You cannot add two collections together with +; build the one you need with
assignment instead.
Collections are also how the standard library exercises take their arguments: one map of the values to prescribe, one of the values to measure and one of options.
import "coach/strength/squat.cl"
squat([weight: 80|kg|, reps: 5], [reps: true, weight: true], [location: true])