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).

returns the first element of t if is not empty, else it returns an empty tab.

returns a new tab corresponding to t deprived of its first element. If t is empty, returns an empty tab.

shrinks the argument to a zero-sized tab (no more elements in t, which is modified in-place).

@concat(t₁, t₂)
returns the concatenation t₁ of and t₂.

@cons(v, t)
returns a new tab made of v in front of t.

@count(t, v)
returns the number of occurrences of v in the elements of t. Also works on string and maps.

returns the dimension of t, i.e. the maximal number of nesting in the elements of t. If t is not a tab, the dimension is 0. A ‟flat” tab (a vector) has dimension 1.

@drop(t, n)
gives a copy of t with its first n, elements dropped if n is a positive integer, and with its last elements dropped if n is a negative integer. If n is a tab of integers, returns the tab formed by the elements of whose indices are not in n.

returns true if there is no element in t, and false elsewhere. Also works on maps.

@find(t, f)
returns the index of the first element of that satisfies the predicate f. The first argument of f is the index of the element and the second is the element itself. Works also on strings and maps.

builds a new tab where the nesting structure oft has been flattened. For example, @flatten ([[1, 2], [3], [[], [4, 5]]]) returns [1, 2, 3, 4, 5, 6].

@flatten(t, l)
returns a new tab where l levels of nesting have been flattened. If l == 0, the function is the identity. If l is strictly negative, it is equivalent to @flatten without the level argument.

plots the elements of the tab as a curve using the external command gnuplot. See the description @gnuplot in the index for further information and variations.

@insert(t, i, v)
inserts “in place” the value into the tab after the index . If is negative, the insertion takes place in front of the tab. If \leq the insertion takes place at the end of the tab. Notice that the function is overloaded and applies also on maps. The form is also used to include a file at parsing time.

@is_prefix, @is_suffix and @is_subsequence
operate on tabs as well as on strings. Cf. the description of these functions in library.

@lace(t, n)
returns a new tab whose elements are interlaced sequences of the elements of the t subcollections, up to size n. The argument is unchanged. For example: @lace([[1, 2, 3], 6, ["foo","bar"]], 12) returns [ 1, 6, "foo", 2, 6, "bar", 3, 6,"foo", 1, 6, "bar" ].

@map(t, f)
computes a new tab where the ith element has the value f(t[i]) .

returns the maximum element among those of t.

@member(t, v)
returns true if v is an element of t. Also works on string and map.

returns the minimum among the elements of t.

@normalize(t, min, max)
returns a new tab with the elements normalized between min and max. If min and max are omitted, they are assumed to be 0 and 1 respectively.

@occurs(t, v)
returns the first index whose value equals the second argument. Also on string and map (the returned value is the corresponding key in a map).

@permute(t, n)
returns a new tab which contains the nth permutation of the elements of t. They are s! permutations, where s is the size of t (and ! is the factorial function). The first permutation is numbered 0 and corresponds to the permutation which rearranges the elements of t in an vector t_0 such that elements are sorted increasingly. The tab t_0 is the smallest element2 among all tab that can be done by rearranging the element of t. The first permutation rearranges the elements of t in a tab t_1 such that t_0 < t_1 for the lexicographic order and such that any other permutation gives a tab t_k lexicographicaly greater than t_0 and t_1. Etc. The last permutation (numbered s! - 1) returns a tab where all elements of t are in decreasing order.

@push_back(t, v)
pushes v to the end of t and returns the updated tab (t is modified in place).

@push_front(t, v)
pushes v to the beginning of t and returns the updated tab (t is modified in place and the operation requires the reorganization of all elements).

@reduce(t, f)
computes f(... f(f(t[0],t[1]), t[2]), ... t[s-1]) where s is the size of t. If t is empty, an undefined value is returned. If it has only one element, this element is returned. Otherwise, he binary operation f is used to combine all the elements in into a single value. For example, @reduce(@+, t) returns the sum of the elements of t.

@remove(t, i)
removes the element at index i in t (t is modified in place). This function is overloaded and also applies to maps.

keeps only one occurrence of each element in t. Elements not removed are kept in order and t is modified in place.

@replace(t, find, rep)
returns a new tab in which a number of elements have been replaced by another. See full description at @remove_duplicate in library.

@reshape(t, s)
builds an array of shape s with the element of tab t. These elements are taken circularly one after the other. For instance @reshape([1, 2, 3, 4, 5, 6], [3, 2]) returns [ [1, 2],[3, 4], [5, 6] ].

@resize(t, s)
increases or decreases the size of t to s elements. If s is greater than the size of t, then additional elements will be <undef>. This function returns a new tab.

returns a new tab with the elements of in reverse order.

@rotate(t, n)
builds a new array which contains the elements of t circularly shifted by n. If n is positive the elements are right-shifted, otherwise they are left-shifted.

@scan(f, t)
returns the tab [ t[0],f(t[0], t[1]), f(f(t[0], t[1]), t[2]), ... ] of the partial result of the reduction (see @reduce). For example, the tab of the factorials up to 10 can be computed by: @scan(@*, [$x : $x in 1 .. 10]).

returns a new tab where the elements of t have been scrambled (their order is randomized). The arguments are unchanged.

returns the number of elements of t.

@slice(t, n, m)
gives the elements of t of indices between n included up to m excluded. If n > m the element are given in reverse order.

sorts in-place the elements into ascending order using <.

@sort(t, cmp)
performs an in-place sort on the elements, putting them in ascending order. The elements are compared using the function cmp. This function must accept two elements of the tab as arguments and returns a value converted to bool. The value returned indicates whether the element passed as first argument is considered to go before the second.

@sputter(t, p, n)
returns a new tab of length n. This tab is filled as follows. The process starts with the first element in t as the current element. Successively for each element e in the result, a random number p' between 0 and 1 is compared with p: if it is lower, then the current element becomes the value of e. If p' is greater, the element after the current element becomes the new current element and this element becomes the value of e.

@stutter(t, n)
returns a new tab whose elements are each elements of t repeated n times. The argument is unchanged.

@take(t, n)
gives the first elements of t if n is a positive integer and the last elements of t if n is a negative integer. If n is a tab of indices, it gives the tab of elements whose indices are in n.

Lists and Tabs

Antescofo’s tabs may be used to emulate lists

  • @car, @cons, @cdr, @drop, @last, @map, @slice and @take are similar to well known functions that exist in Lisp.

  • @concat returns the concatenation (append) of two lists.

  • Arithmetic operations on vectors are done pointwise, as in some Lisp variants.

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 Manipulations @binary_search    @car    @cdr    @clear    [@clone]    @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    @parse    @permute    @push_back    @push_front    @range    @reduce    @remove    @remove_duplicate    @replace    @reshape    @resize    @reverse    @rotate    @scan    @scramble    @size    @slice    @sort    @sputter    @stutter    @succession    @tab_history    @tab_history_date    @tab_history_rdate    @take    @to_num   

  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.