Trainerflow Docs trainerflow.com
The language › Collections

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])