Overview

The SCAN statement ‘sc’ iterates over data structures in the MUFL programming language, providing options for binding keys and values, nested iteration, filtering, and conditional execution. Here is a simple example that iterates over a list of integer numbers and prints them:

sc [1, 2, 3] -- (->value) { _print value "\n". }

Prints:

1
2
3

Syntax

scan := [label;]sc <source> <layers> { body }
source := <any mufl expression>
layers := -- <layer> -- <layer> ... 
layer := range-optional bindings-optional explicit-value-or-set-optional condition-optional
range := [ begin-value-optional .. end-value-optional ]
bindings := ( key-name-optional -> value-name-optional )
explicit-value := = value
explicit-set := = (nested-scan)
condition := ?? expression

Note, that label is marked with semicolon

Layers

The ‘sc’ statement is designed to iterate over data structures that are often nested. Consequently, each layer expected within the data structure requires an instruction in the scan statement. Layers of the statement are always preceded by special delimiter --

Components

  • label - Optionally, names the loop for purposes of using the name in break and continue statements.
  • source - The data structure to iterate over, such as a list or dictionary.
  • range - Has the form [begin..end], for example [1..10]. Range is an optional instruction to begin and end the iteration at a specified value.
  • bindings - The bindings clause names the variables that will be bound to the keys and values of the data structure during the iteration. For example, binding (a->b) instructs the evaluator to bind the keys to name a and values to name b
  • condition - The condition clause starts with ?? and takes a predicate expression. The evaluator uses the condition clause to filter the values being iterated over – values that don’t pass the condition expression will be skipped
  • explicit-value-or-set - This instruction specifies a sub-set of values aside of which all others will be skipped

    Examples

1. Basic Iteration

Iterating over a list and printing its elements. This scan statement as a single layer, and binds all the values of the list to name value using the binding instruction (->value). Note that the key binding is omitted.

scan [1, 2, 3] has value do _print value "\n". end

Prints:

1
2
3

2. Iterating with Keys

Iterating over a dictionary and printing key-value pairs:

sc ("a"->1, "b"->2, "c"->3) -- (key->value) { _print key " -> " value "\n". }

Prints:

a -> 1
b -> 2
c -> 3

3. Nested Iteration

Iterating over a nested data structure. Here we introduce a second layer into the statement, because the data structure is a two-layer dictionary representing a two-dimentional matrix of numbers.

sc [[1,2],[3,4]] 
    -- (i->)                            // bind keys to name i
    -- (j->val)                         // bind keys to j and values to val
{ 
    _print "[" i "," j "]=" val "\n". 
}

Prints:

[0,0]=1
[0,1]=2
[1,0]=3
[1,1]=4

4. Filtering with Conditions

Using a filter condition to print only the elements with odd values:

sc [1, 2, 3, 4, 5] 
    -- (->value)??value % 2 == 1 
    {
        _print value "\n". 
    }

Prints:

1
3
5

5. Filtering with Nested Scan

Using a nested scan statement to filter elements based on another data structure:

sc [1,2,3,4,5] 
    -- (->value) =(sc [0,2,3])           // only iterate the provided list over a subset of indices 0,2,3
    {
        _print value "\n". 
    }

Prints:

1
3
4

6. Range-based Filtering

Using [from..until] to define a range of keys:

sc [1,2,3,4,5,6,7,8] 
    -- [2..4] (->value) 
    {
        _print value "\n". 
    }

Prints:

3
4
5

the end-value of the range is inclusive. Add < as follows to specify that the end of the range is exclusive [begin..<end]

7. Destructuring Binding

Values in the scan statement can be destructured on the fly using standard patterns.

For more information, refer to Destructuring with the bind Statement.

Binding multiple variables to elements in the data structure:

sc [[1, "hello"], [2,"world"]] -- (i->[number, name])
{
    _print i "->[" number "," name "]\n". 
}

Prints:

0->[1,hello]
1->[2,world]

8. Destructuring Binding and Filtering with Conditions

When filtering values with the condition clause, the condition may refer to any variables accessible in the current scope, as well as any named values that precede it, including the names of destructured values.

Modifying the preceding example to make use of a where condition that accesses the key i and the destructured value number:

sc [[1, "hello"], [2,"world"]] 
    -- (i->[number, name])??number % 2 == 0
    {
        _print i "->[" number "," name "]\n". 
    }

Prints:

1->[2,world]

9. Using number sequences with scan

Instead of the traditional for-loop construct, use the sc statement to iterate over a number sequence.

Sum all numbers between 1 and 10 inclusive:

sum = 0. 
sc (1..10) -- (->val) 
{
    sum -> sum + val. 
}
_print sum.

Prints:

55