Lesson 12: List Annotations

Intermediate

When you need to apply an operation to every element of a list, you could write a loop — but Phograph offers a more declarative approach. List annotations tell the runtime to automatically map a node over a list of inputs.

ListMap

The ListMap annotation marks a node so that it runs once per element of its list input. The results are collected into a new list.

  constant [1, 2, 3, 4] --> [* (ListMap)] <-- constant 10
                              outputs: [10, 20, 30, 40]

Here the * node is annotated with ListMap. The left input is a list, so the node fires four times — once per element — multiplying each by 10.

Scalar broadcasting

When one input is a list and another is a scalar (single value), the scalar is broadcast — reused for every element. In the example above, 10 is a scalar and is paired with each element of the list.

  constant ["a","b","c"] --> [concat (ListMap)] <-- constant "-suffix"
                              outputs: ["a-suffix", "b-suffix", "c-suffix"]

Partition

The Partition annotation splits a list into two lists based on a boolean predicate. Elements for which the predicate returns true go into the first output; the rest go into the second.

  constant [1,2,3,4,5,6] --> [is-even? (Partition)]
                              output 1 (true):  [2, 4, 6]
                              output 2 (false): [1, 3, 5]

Compared to explicit loops

List annotations and loops can accomplish similar tasks, but they have different strengths:

  Loop version of the multiply example:

  constant [1,2,3,4] --> [loop: map-times-10]
  shift register "result" starts at []
  body: element * 10 --> append to result
  output: [10, 20, 30, 40]

  ListMap version (much simpler):

  constant [1,2,3,4] --> [* (ListMap)] <-- constant 10
  output: [10, 20, 30, 40]

What you learned