Comprehensions and Iteration

Shane Brinkman-Davis Delamore edited this page May 4, 2018 · 44 revisions

Related: Control Structures ('while' and 'until' loops are described there)

Keywords

Each iterates through the source container:

• `array` - returns a new array; values are the results of the with-block
• `object` - returns a new object; values are the results of the with-block
• `find` - stops when the desired result is found; returns the result from the last with-block
• `each` - returns the source container

Basic Examples

```# new array, with written values doubled before storing them
array  v from 1, 2, 3      with v * 2           # > [2, 4, 6]
array  v from a:1, b:2     with v * 2           # > [2, 4]
array  v from null         with v * 2           # > []

# new object with every property-value doubled
object v from 1, 2, 3      with v * 2           # > 0: 2, 1: 4, 2: 6
object v from a:1, b:2     with v * 2           # > a: 2, b: 4
object v from null         with v * 2           # > {}

# find and return the first value in the source list that is greater than  2
find   v in 1, 2, 3, 2     when v > 2           # > 3
find   v in a:1, b:4, c:1  when v > 2           # > 4

# log or echo each number to the programmer's console
each   v in 1, 2, 3        do console.log v     # > [1, 2, 3]```

Comprehensions Translated to English

CaffeineScript's comprehensions are designed to be streamlined versions of English-language sentences.

• `array value, index from source with ...`

• create a new `array`
• using each `value` and `index`
• `from source`
• to compute each new element `with ...`
• `object value, key from source with ...`

• create a new `object`
• using each `value` and `key`
• `from source`
• to compute each new property-value `with ...`
• `each value, index in source do ...`

• for `each value` and `index in source`, `do ...`
• `find value in source when ...`

• `find` the first `value` `in source` `when ...` is true
• `find value in source when ... do ...`

• `find` the first `value` `in source` `when ...` is true and `do ...`

Canonical Form

```# one-liner
(array|object|each|find) [myValue[, myKey] from] mySource [into myExpression] [when myExpression] [with myExpression]

# with-block
(array|object|each|find) [myValue[, myKey] from] mySource [into myExpression] [when myExpression]
# with-block goes here, last statement is the with-block return-value
statements```

Sources

Sources can be `Arrays`, `Objects`, or false-ish.

• array-source: values are the array entries, and the keys become the array indices
• object-source: keys and values are the object properties' keys and values. Iteration uses the `for (k in b) {}` method, so it includes own-properties and inherited-properties. To only iterate over own-properties, add a when-clause: `when source.hasOwnProperty key`
• false-ish values: `when` and `with` are not executed, but the following will be returned for array, object, and find: [], {}, undefined. For 'each', the exact source-value passed in will be returned.

Examples:

• If `mySource` is an array like `[57 12 92]`
• `myValue` is the value of each element: `57, 12, 92`
• `myKey` is the index of each element: `0, 1, 2`
• If `mySource` is an object like: `a:57, b:12, c:92`
• `myValue` is the value of each property: `57, 12, 92`
• `myKey` is the name of each property: `:a, :b, :c`
• A false-ish `mySource` is treated like an empty array: `[]`

Default `with` block

The default with-block always returns the current iteration's value:

```array user from users
# default: with user ```

There is one special case that follows the rule, but it may surprise you. You can assign to the value-variable in the `when` expression, and that assigned value is what the default-with-block returns:

```# returns username for the first value with a username
find v from users when v = v.username

array v from users when v = v.username

# returns all values that have usernames
array v from users when v.username```

Object comprehension `with-key` block

If the comprehension-type is `object`, you can additionally specify a `with-key` block. This works much like the `with` block except its return-value is used as the key when assigned the value to the returned object.

Example:

```object v from 1, 2, 3 with-key :a + v
# out: a1: 1, a2: 2, a3: 3```

Into

For `array`, `object`, or `each`, but not 'find', the `into` keyword can override the default return value, and establish the object the values are added to--in the case of `array` or `object`.

```# shall copy into another array
arr1 = 1 2
arr2 = 2 3
array arr2 into arr1
# EFFECT: modifies arr1
# OUT: arr1
# > 1 2 2 3```
```# merge one object into another
obj1 = a: 1, b: 2
obj2 = b: 3, c: 3
object obj2 into obj1
# EFFECT: modifies obj1
# OUT: obj1
# > a: 1, b: 3, c: 3```

Range Iteration

If the comprehension type is `array`, instead of iterating over a source array or object, you can instead iterate over a range. To do this, you must specify either a `to` clause or a `til` clause (both is not allowed). There is only one comprehension variable for range iteration, the value of each loop (unlike other iterations where there is a value and key or index).

Range iteration clauses:

• `to`: iterate up to AND INCLUDING its value
• `til`: iterate up to BUT NOT INCLUDING its value
• `from/in`: start-value for the iteration. Default: 0 (yes, unlike any other comprehension, the from-clause is optional for range-iteration)
• `by`: amount to increment, or if negative, decrement, by each iteration. Default: if (toClause || tilClause) > fromClause then 1 else -1.
• `with` and `when`: work just like other comprehensions

Examples:

```array to 5                     # [] 0 1 2 3 4 5
array to -3                    # [] 0 -1 -2 -3
array to 5 by 2                # [] 0 2 4
array til 5                    # [] 0 1 2 3 4
array to 5 with 1              # [] 1 1 1 1 1 1
array a to 5 when a %% 2 == 0  # [] 0 2 4
array from 2 to 5              # [] 2 3 4 5```

Examples

array

```myArray = 1 2 3

# shallow-clone of myArray
array myArray
# > 1, 2, 3

# with values doubled
array value from myArray with value * 2
# > 2, 4, 6```

object

```# new object where they keys and values are the values from myArray
object myArray
# > 1: 1, 2: 2, 3: 3

# with doubled values
object value from myArray with value * 2
# > 1: 2, 2: 4, 3: 6```

find

```# find the first value greater than 2
find value from myArray when value > 2

# same by using 'with'
find value from myArray with if value > 2 then value
# > 3

# same, but the found-value returned doubled
find value from myArray when value > 2 with value * 2
# > 6```

Shallow Clone

```# mySource can be an array or object

# OUT: new array of all the values
array mySource
array from mySource
array v from mySource
array v from mySource with v
array v from mySource
v

# OUT: new object
#   if mySource is an object
#     with all the props from mySource
#   if mySource is an array
#     with all the values mapped to themselves
#     i.e. all the values from the array are
#     assigned with: out[value] = value
object mySource
object from mySource
object v from mySource
object v from mySource with v
object v from mySource
v
```

No Tail-Form

Unlike CoffeeScript, tail-comprehensions are not supported. They create too many ambiguities, in my opinion don't add significantly to readability, and anyway CaffeineScript has an alternative one-liner-comprehension form using the `with` keyword.

One big problem with tail-comprehensions is this ambiguity:

```# CoffeeScript
a = v for v in source

# QUESTION: Does CoffeeScript compile the previous line
# to this?
a = for v in source
v

# or this?
for v in source
a = v
```

ANSWER: CoffeeScript compiles it to the latter, which, unfortunately, is the least useful option.

CaffeineScript, instead, allows a cleaner and clearer solution:

```# the two options are clearly different:
a = array v from source with v
array v from source with a = v```

This is one case where having one extra token really helps. However, CaffeineScript gives you ways to streamline common cases:

```# all 3 are the same
a = array v from source with v
a = array v from source
a = array source```

Aliases: `with/do`, `in/from` and `into/returning`

Solely for the purpose of readability, two of the keywords have semantically-identical aliases:

• `with` is semantically identical to `do`
• `from` is semantically identical to `in`
• `into` is semantically identical to `returning`

Preferred aliases

Depending on the type of comprehension you are using, different keywords fit natural English better. I suggest using:

```# declarative language:
# 'from' is preferred because it suggests the values and keys are
#   coming 'from' somewhere and going 'to' somewhere
# 'with' is preferred because it suggests, ideally, that it should be side-effect-free
array ... from ... into ... with
object ... from ... into ... with

# 'do' suggests there is a side-effect
# (otherwise, 'each' is probably the wrong choice)
each ... in/from ... returning ... do

# find-in is better english (can a grammar expert tell me why???)
# Maybe because in everyday life you "look in" a box, in order to
# identify its contained objects.
find ... in ... with
find ... in ... when ... do```
Clone this wiki locally
You can’t perform that action at this time.
Press h to open a hovercard with more details.