Ion Fusion Documentation
Release 0.38a1-SNAPSHOT (2026-04-16T19:45:37.790Z)

Module /fusion/for

The for-family of syntax forms enables concise, consistent iteration over many different varieties of aggregate data types, known as series types. The variants differ in how the results are accumulated, but all accept the same inputs and traverse them in the same manner.

The basic form is for itself:

(for [(a [1, 2]),
      (b (sexp "a" "b"))]
  (displayln (sexp a b)))

Here, a and b are bound successively to the individual elements of a list and a sexp, respectively, then the body (a call to display) is evaluated with those bindings. Next, a and b are given fresh storage locations and new bindings to the next elements of the series.

Since for always returns void, it's used to perform side effects; the example above prints two lines:

(1 "a")
(2 "b")

For-clauses

Common to all for variants is the for-clause, a syntax fragment that declares one or more identifiers and an expression evaluating to a series value. Syntactically, these are similar to the binding clauses of the let-family of binding forms, allowing one or more bound identifiers:

for-clause := (id series_expr)
            | ((id ...+) series_expr)

The second form is used with series that produce multiple values:

(for_list [((name value) { "a": 1, "b": 2 })]
  (display_to_string name "=" value))
=>
["a=1", "b=2"]

In all forms, the series_exprs are evaluated once before iteration begins, and the result must be a value of one of the series types. The number of ids in a for-clause must match the number of values produced by each step of the series.

Series Types

A series is a value that can be traversed by the for forms. Traversing a series consists of a sequence of steps, and each step produces one or more values (consistent through the entire traversal). Depending on the type of series, the order in which the series is traversed may or may not be predictable.

For more information on the types that can be used as series, and other ways to create them, see /fusion/series.

Flat and Nested Traversal

The family variants come in pairs, starting with for or fors. When given a single for-clause, these behave identically, but given multiple clauses they behave differently.

A for expression traverses all of its series in lock-step, taking one step on each series for each evaluation of the body:

(for [(a [1, 2]),
      (b [5, 7])]
  (display (sexp a b)))
=>
(1 5)(2 7)

A fors expression traverses its series as nested loops, as if each clause were given in its own nested for expression:

(fors [(a [1, 2]),
       (b [5, 7])]
  (display (sexp a b)))
=>
(1 5)(1 7)(2 5)(2 7)

This is similar to the distinction between let and lets, and like those forms the bound identifiers have different scopes in the two forms.

for syntax
(for [for-clause, ...] body ...+)

for-clause := (id series_expr)
            | ((id ...+) series_expr)

Iterates series in parallel, binding the corresponding ids to the values produced at each step and then evaluating body.

The series_exprs are first evaluated left-to-right, and each must return a series value. While every series can produce more elements, they are retrieved in left-to-right order, bound to their ids at new storage slots, and then the body is evaluated. The results of the body are ignored; the result of for is always void.

If no for-clauses are provided, then the body is executed once.

(for [(a [1, 2]),
      (b [5, 7])]
  (display (sexp a b)))
=>
(1 5)(2 7)
for_fold syntax
(for_fold
  [(accum-id init-expr), ...]
  [for-clause, ...]
  body ...+)

Iterates using explicit accumulators. The first clause is used to initialize any accumulator bindings before iteration begins; new accumulator locations are used for each cycle. The final body expression must produce the same number of values as there are accum-ids, and those become the accumulator values for the next cycle.

The results of the for_fold expression are the final accumulator values.

(for_fold
  [(result "")]
  [((name value) { "a": 1, "b": 2 })]
  (string_append result
    (if (== result "") "" " and ")
    (display_to_string name "=" value)))    => "a=1 and b=2"
for_list syntax
(for_list [for-clause, ...] body ...+)

Like for, but returning a stretchy list of the body results.

(for_list
  [(even [0, 2, 4]),
   (odd  [1, 3, 5])]
  (+ even odd))             =>  [1, 5, 9]
for_sexp syntax
(for_sexp [for-clause, ...] body ...+)

Like for, but returning a sexp of the body results.

(for_sexp
  [(even [0, 2, 4]),
   (odd  [1, 3, 5])]
  (+ even odd))             =>  (1 5 9)
for_struct syntax
(for_struct [for-clause, ...] body ...+)

Like for, but returning a struct. The body must return two values, a field name (string or symbol) and its corresponding element.

(for_struct
  [(key (quote (a b c))),
   (val [1, 2, 3])]
  (values key val))             =>  {a:1, b:2, c:3}

If the same field name is returned by multiple evaluations of the body, the resulting struct will have corresponding repeated fields:

(for_struct
  [(key ["a", "b", "a"]),
   (val [1, 2, 3])]
  (values key val))             =>  {a:1, a:3, b:2}
fors syntax
(fors [for-clause, ...] body ...+)

Like for, but the series are iterated in nested loops. The scope of each binding identifier includes the series_exprs of later for-clauses, as well as the body.

(fors [(a [1, 2]),
       (b [5, 7])]
  (display (sexp a b)))
=>
(1 5)(1 7)(2 5)(2 7)
fors_fold syntax
(fors_fold
  [(accum-id init-expr), ...]
  [for-clause, ...]
  body ...+)

Like for_fold, except the series are iterated in nested loops instead of in parallel. Each clause's bindings are made available to the following clauses as well as the body.

fors_list syntax
(fors_list [for-clause, ...] body ...+)

Like fors, but returning a stretchy list of the body results.

(fors_list [(a [1,2]),
            (b [5,7])]
  (* a b))                             => [5, 7, 10, 14]

(define data [{a:[ {b:1}, {b:2} ]},
              {a:[ {b:3}, {b:4} ]} ])
(fors_list [(x data),
            (y (. x "a"))]
  (. y "b"))                           => [1, 2, 3, 4]

(define data [[[1],[2]],[[3],[4]]])
(fors_list [(x data),
            (y x),
            (z y)]
  z)                                   => [1, 2, 3, 4]
fors_sexp syntax
(fors_sexp [for-clause, ...] body ...+)

Like fors, but returning a sexp of the body results.

(fors_sexp [(a [1, 2]),
            (b [5, 7])]
  (* a b))                             => (5 7 10 14)
fors_struct syntax
(fors_struct [for-clause, ...] body ...+)

Like fors, but returning a struct. The body must return two values, a field name (string or symbol) and its corresponding element. As with for_struct, the result can have repeated fields:

(fors_struct [(v [1, 2]),
              (k ["a", "b"])]
  (values k v))                 => {a:1, a:2, b:1, b:2}