Tab values (tables) are used to define simple vectors and more. They can be defined by giving the list of their elements:

The tab keyword is case-insensitive and optional. For example:

          $t := tab [0, 1, 2, 3]    ; or
          $t := [0, 1, 2, 3]        ; the `tab' keyword is optional

these statements assign a tab with 4 elements to the variable $t.

Tables are compared in lexicographical order. In a boolean expression, an empty tab is false. Otherwise, it's true.

Elements of a tab can be accessed through the usual square bracket ...[...] notation. Element indexing starts at 0, so $t[n] refers to the (n+1)th element of the tab refered by $t. Notice that the arguments of the square bracket are expressions1. Elements of a tab can be any kind of value, even a tab, which would create a multidimensional array. Additionally, one tab can contain multiple different types of objects.

The ForAll action can be used to iterate through all of the elements in a tab. A tab comprehension can be used to build new tab by filtering and mapping tab elements. There are also several predefined functions to transform a tab.

Multidimensional tab

Elements of a tab are arbitrary, so they can be other tabs. Nested tabs can be used to represent matrices and multidimensional arrays. For instance:

          [ [1, 2], [3, 4], [4, 5] ]

is a 3 \times 2 matrix that can be interpreted as 3 lines and 2 columns. For example, if $t is a 3 \times 2 matrix, then the first element of the second line is accessed by the expression

          $t[1, 0]  ; equivalent form

The function @dim can be used to query the dimension of a tab, that is, the maximal number of tab nesting found in the tab.

A multidimensionnal array is a homogeneous tab: a tab of tab elements is an multidimensional array (of dimension 1) and a tab whose elements are multidimensionnal arrays of the same dimension and size are also multidimensional arrays.

If a tab's argument is a multidimensional array, the function @shape returns a tab of integers wherein the element i represents the number of elements in the ith dimension. For example

          @shape( [ [1, 2], [3, 4], [4, 5] ] )    [3, 2]

The function returns 0 if the argument is not a well-formed (dimensionally consistent) array. For example

          @shape( [1, 2, [3, 4]] )        0

Note that for this argument, @shape returns ::antescofo 0 because the argument is a tab nested into a tab, but it is not an array because the element of the top-level tab are not homogeneous. The tab

          [ [1, 2], [3, 4, 5] ]

also fails to be an array, despite that all elements are homogeneous, because these elements are not of the same size.

Tab Comprehension

If a tab is specified by giving the list of its elements, the definition is said in extension. The tab [ ... ] construction is an expression that defines a tab in extension.

A tab can also be defined in comprehension. A tab comprehension is an expression to build a tab from an existing tab or on some iterators. The general form of a tab comprehension is

          [ output_expression | $var in input_set , predicate ]

where output_expression is an expression, $var is a variable identifier, input_set is an expression evaluating to a tab or an integer or the construction e₁.. e₂ : e₃, and predicate is a boolean expression. More precisely:

This construction follows the form of the mathematical set-builder notation (set comprehension). For instance

          [ e | $x in t ]

generates a tab of the values of the output expression e by running through the elements specified by the input set t. If t is a tab, then takes all the values in the tab. For example:

          [ 2*$x | $x in [1, 2, 3] ]     [2, 4, 6]

The input set t may also evaluate to a numeric value n: in this case, take all the numeric values between 0 and n excluded by unitary steps:

          [ $x | $x in (2+3) ]        [0, 1, 2, 3, 4]
          [ $x | $x in (2 - 4) ]      [0, -1]

Note that the variable $x is a local variable visible only in the tab comprehension: its name is not meaningful and could be any variable identifier (but beware that it can mask a regular variable in the output expression, in the input set or in the predicate).

The input set can be specified by a range giving the starting value, the step and the maximum value:

          [ e | $x in start .. stop : step ]

The specification start .. stop specifies a range where start is included and stop is excluded. If the specification of the step is not given, it value is +1 or -1 following the sign of (stop - start). The specification of start is also optional: in this case, the variable will start from 0. For example:

          [ $s[$i] + $t[$i]  | $i in @size($s) ]

creates a tab whose elements are the pointwise sums of $s and $t (assuming that they have the same size). Notice that expression $i in (@size($s)) enumerates the indices of $s. Expression

          [ @sin($t) | $t in -3.14 .. 3.14 : 0.1 ] 

generates a tab of 62 elements: \sin(-3.14), \sin(-3.04), ..., \sin(3.04).

Tab comprehension may specify a predicate to filter the members of the input set:

           [$u | $u in 10, $x % 3 == 0]     [0, 3, 6, 9]

filters the multiple of 3 in the interval [0, 10). The expression used as a predicate is given after a comma, at the end of the comprehension.

Tab comprehensions are ordinary expressions, so they can be nested. This can be used to create a tab of tabs. Such a data structure can be used to make matrices:

          [ [$x + $y | $x in 1 .. 3] | $y in [10, 20, 30] ]
              [ [11, 12], [21, 22], [31, 32] ]
More Examples of Tab Comprehension

Here are some additional examples of tab comprehensions to illustrate the syntax:

          $z := [ 0 | (100) ]        ; builds a vector of 100 elements, all null

In this example, the iterator variable is absent. In this case, the input set is constrained to be an expression between parentheses and evaluating to either a number or a tab (it cannot be a range).

          $s := [ $i | $i in 40, $i % 2 == 0 ] ; lists the even numbers from 0 to 40
          $t := [ $i | $i in 40 : 2]           ; same as previous 
          $u := [ 2*$i | $i in (20) ]          ; same as previous

          ; equivalent to ($s + $t) assuming arguments of the same size
          [ $s[$i] + $t[$i] | $i in @size($t) ] 

          $m := [ [1, 2, 3], [4, 5, 6] ]        ; builds a matrix of 3x2 dimensions
          $m := [ [ @random() | (10) ] | (10) ] ; builds a random 10x10 matrix

          ; transpose of a matrix $m
          [ [$m[$j, $i] | $j in @size($m)] | $i in @size($m[0])]

          ; scalar product of two vectors $s and $t
          @reduce(@+, $s * $t)

          $v := [ @random() | (10) ] ; builds a vector of ten random numbers
          ; matrice*vector product
          [ @reduce(@+, $m[$i] * $v) | $i in @size($m) ]

          ; squaring a matrix $m, i.e. $m * $m
          [ [ @reduce(@+, $m[$i] * $m[$j]) | $i in @size($m[$j]) ] 
            | $j in @size($m) ]

Mutating a tab's element

A tab is a mutable data structure : one can change an element within this data structure. Although a similar syntax is used, changing one element in a tab is an atomic action different from the assignment of a variable. For example

          let $t[0] := 33
          $t[0] := 33   ; the 'let' is optional if the tab is denoted by a variable

changes the value of the first element of the tab referred by $t to the value 33. The general syntax is:

Unless the tab is referred to by a variable, the let keyword is mandatory. It is required when the expression in the left hand side of the assignment is more complex than a variable, a simple reference to an array element or a simple access to a local variable of an exec. See sect. Assignment.

Because the tab to mutate can be referred to by an arbitrary expression, one may write something like:

          $t1 := [0, 0, 0]
          $t2 := [1, 1, 1]
          @fun_def @choose_a_tab() { (@rand(1.0) < 0.5 ? $t1 : $t2) } 
          let @choose_a_tab()[1] := 33

that will change the second element of a tab chosen randomly between $t1 and $t2. Notice that:

          let @choose_a_tab() := [2, 2, 2]  ; invalid statement

raises a syntax error: this is neither a variable assignment nor the update of a tab element (there are no indices to access such element).

Elements of nested tabs can be updated using the multi-index notation:

          $t := [ [0, 0], [1, 1], [2, 2] ]
          let $t[1,1] := 33

will change the tab referred by to [[0, 0], [1, 33], [2,2]]. One can change an entire “column” using partial indices:

          $t := [ [0, 0], [1, 1], [2, 2] ]
          let $t[0] := [33, 33]

will produce [[33, 33], [1, 1], [2, 2]]. Nested tabs are not homogeneous, so the value in the r.h.s. can be anything.

Sharing and copying a tab

In the introduction of section Values, we mention that a compound value is refered through a handle (or pointer). So, the same compound value can be shared between variables or shared between nested data structures.

This can be illustrated by the following example:

          let $t := [0, 0, 0]
          $u := $t
          let $t[0] := 333
          print $u  ; will print [333, 0, 0]

After the assignment to $t, the value referenced by $u has mutated, because the same tab is referenced by $t and $u.

Assignment of a tab, and more generally a compound value, does not imply the copy of the referenced value. The function @copy can be used to create a fresh value:

          let $t := [0, 0, 0]
          $u := @copy($t)
          let $t[0] := 333
          print $u  ; will print [0, 0, 0]

The sharing of data structures is useful: the same tab can be referred to from many different places in the score and one update is visible by all the tab referrers. However, it may be troublesome. The following code is intended to create a 3×3 null matrix $m0

   let $row := [0, 0, 0]
   let $m0 := [ $row, $row, $row ]

but this is not exactly the case. As a matter of fact, if we want to turn $m0 in the identity matrix using assignment:

   let $m0[0, 0] := 1
   let $m0[1, 1] := 1
   let $m0[2, 2] := 1

what we obtain is a a 3×3 matrix full of 1: because all the row of $m0 refers to the same tab.

Assignment versus Mutating a tab's element

Changing the value of a tab's element is not a variable assignment: the variable has not been “touched”, it is the value referred by the variable that has mutated.

The difference between variable assignment and mutating one element in a tab is more evident in the following example:

          let [0, 1, 2][1] := 33

where it is apparent that no variable at all is involved. The previous expression is perfectly legal: it changes the second element of the tab [0, 1, 2]. This change will have no effect on the rest of the program because the mutated tab cannot be refered elsewhere but this does not prevent the action to be performed.

An important consequence is that mutating a tab elements does not trigger a whenever even if a variable is involved in the assignment. It is however very easy to trigger a whenever watching a variable referring to a tab, after the update of an element: it is enough to assign it to itself:

          $t := [1, 2, 3]
          whenever ($t[0] == 0) { ... }
          let $t[0] := 0 ; does not trigger the whenever
          $t := $t       ; the whenever is triggered       

We can mutate the first element of $t but this does not trigger the whenever. To do so, we assign the variable to itself. As a matter of fact, a whenever watches the assignment of a set of variables , NOT the mutation of the values referred by these variables.

Listable Operators

Usual arithmetic and relational operators are listable (cf. other listable functions in annex Library).

When an operator op is marked as listable, the operator is extended to accept tab arguments in addition to scalar arguments. Usually, the result of the application of op on tabs is the point-wise application of op to the scalar elements of the tab. But for relational operators (predicates), the result is the predicate that returns true if the scalar version returns true on all the elements of the tabs. If the expression mixes scalar and tab, the scalars are extended pointwise to produce the result. So, for instance:

          [1, 2, 3] + 10                 [11, 12, 13]
          2 * [1, 2, 3]                  [2, 4, 6]
          [1, 2, 3] + [10, 100, 1000]    [11, 102, 1003]
          [1, 2, 3] < [4, 5, 6]          true
          0 < [1, 2, 3]                  true
          [1, 2, 3] < [0, 3, 4]          false

Tab manipulation

Several functions exist to manipulate tabs intentionally, i.e., without referring explicitly to the elements of the tab. We briefly describe some of these functions. The Library exhaustively describes all tab related functions (click on function name to access the page dedicated to the function).

Lists and Tabs

Antescofo’s tabs may be used to emulate lists

In particular, the operators @cons, @car and @cdr can be used to destructure and to build a tab. They can be used to define recursive functions on tabs in a manner similar to that of recursive functions on lists. However, it builds a new tab unlike the operation cdr on list in Lisp. A tab comprehension is often more convenient and usually more efficient than a recursive definition.

The Library documents all Tab Related Functions @car,    @cdr,    @clear,    @concat,    @cons,    @copy,    @count,    @dim,    @domain,    @drop,    @empty,    @find,    @flatten,    @gnuplot,    @insert,    @iota,    @is_list,    @is_prefix,    @is_subsequence,    @is_suffix,    @lace,    @last,    @listify,    @map,    @max_val,    @median,    @member,    @normalize,    @occurs,    @permute,    @push_back,    @push_front,    @range,    @reduce,    @remove,    @remove_duplicate,    @replace,    @resize,    @reverse,    @rotate,    @scan,    @scramble,    @size,    @slice,    @sort,    @sputter,    @stutter,    @succession,    @tab_history,    @tab_history_date,    @tab_history_rdate,    @take   

  1. The arguments of the square brackets are expressions so one can write, e.g. (@f($x))[$n] to access the value of the $nth element of the tab returned by a function @f. Parentheses are used to apply the brackets to the value returned by @f instead on $x

  2. smallest for the < ordering. On tabs, this ordering corresponds to the lexicographic ordering.