Functions

image from a clock mechanism

If computerized actions in your score observe very repetitive conceptual patterns similar to electronic leitmotifs, then you might want to simplify your score by defining Functions, Process, Macros, Actors and Patterns, which recognize those patterns through user-defined entities and arguments.

Functions are described in this chapter, processes in the next one and macros in chapter macro. Functions and processes are first class values in the language: they can be elements in a tab or a map, passed as arguments to a function or a process, etc. See paragraph Macro versus Function versus Process for a comparison of the three constructions. Actors and Patterns are not primitive entities: they are internally rewritten respectively in process and nested whenever.

Functions live in the domain of expressions and values:

These properties do not hold for macros nor processes (see page Macro versus Function versus Process for a comparison of the three constructs).

Antescofo offers several kind of functions. NIM and MAP are two examples of data values that can act as functions: they can be applied to an argument to provide a value. Maps are extensional functions: they are qualified as extensional because they enumerate explicitly the image of each possible argument in the form of a (key,value) list. NIMs are also a kind of extensional function: if the image y of each argument x is not explicitly given, the association between the argument and y is taken in a limited set of possibilities constrained by the breakpoints.

In this chapter, we will focus on intentional functions. Intentional functions f are defined by arbitrary rules (i.e. by an expression) that specify how an image f(x) is associated to an element x.

Some intentional functions are predefined and available in the initial environment like the IEEE mathematical functions. See Library for a description of more than 190 predefined functions. There is no difference between predefined intentional functions and user’s defined intentional functions except that in a Boolean expression, a user’s defined intentional function is evaluated to true and a predefined intentional function is evaluated to false.

Intentional functions can be defined (only at the top-level of the score) and associated to an @-identifier using the @fun_def construct. For example, the following code shows a convenient user-defined function that converts MIDI pitch values to Hertz. Any call to (for example) @midi2hz(69) anywhere in the action language where an expression is allowed (inside messages, etc.) will be replaced by its value at run-time.

        @fun_def midi2hz($midi)
        {
            440.0 * exp(($midi-69) * log(2) / 12 )
        }  

The example above is rather dubious since we do not use any of Antescofo’s interactive facilities! The following example is another classical user-defined function that employs the system variable $RT_TEMPO (musicians’s real-time recognised tempo in BPM) with the goal of converting beat-time to milli-seconds using the latest tempo from musician. This function has been used in various pieces to simulate trajectories of effects based on score time (instead of absolute time). Note that indentation and carriage-returns do not matter:

        @fun_def beat2ms($beats) {    1000.*$beats*60.0/$RT_TEMPO   }  

Function definition

The two examples above are show simple functions whose bodies are just one expression:

          @fun_def name($arg1, $arg2, ...) { expression }

The name of the function can be a simple identifier or an @-identifier. But in the rest of the score, the function must be referred through its @-name. For the moment, there is no-anonymous functions in Antescofo, no optional arguments, no named arguments.

Writing a large function can become cumbersome and may involve the repetition of common sub-expressions. To face these problems, since version 0.8, the body of a function can also be an extended expression. See three kinds of expressions for a presentation of the three classes of expressions.

Extended expressions

An extended expression is an optional local variable declaration introduced by @local, followed by an arbitrary sequence of

  1. simple expressions optionally preceded by the return keyword

  2. local or global variable assignments using := (right hand side is a simple expression)

  3. iteration expressions Loop and ForAll whose sub-expressions are extended expressions

  4. extended conditional expressions if .. else ... and … whose sub-expression are extended expressions

  5. Max/PD messages

  6. abort actions.

This structure is formalized by the diagram below where 'cexpr' refers to closed expressions, 'sexpr' to simple expressions and 'Exp' to extended expression.


Rationale of Extended Expressions. An extended expression is allowed only in the body of a function. This is not because they have something special: they are no more than “ordinary” expressions. The only motivation behind this constraint is to avoid syntactic ambiguities when the score is parsed. With extended expressions, function definitions are similar to C function definitions that mix expression and statement (but they are differences, see below). As a matter of fact, Antescofo's functions mix expressions and (a few kind of) actions. Only a limited set of actions are allowed in functions: some of the actions that have zero-duration. The rational is the following: a function call must have no extent in time and the evaluation must be more efficient than a process call.

After some simple introductory examples, we detail these extended constructions.

First Examples

The function definition

          @fun_def polynomial($x, $a, $b, $c, $d)   
          {
              @local $x2, $x3
              $x2 := $x * $x
              $x3 := $x2 * $x
              return $a*$x3 + $b*$x2 + $c*$x + $d
          }

computes ax^3 + bx^2 + cx +d: the extended expression specifying the function body introduces two local variables used to factorize some sub-computations. The result to be computed is specified by the expression after the return statement.

In function

          @fun_def fact($x)
          {
              if ($x <= 0) { return 1 }
              else { return $x * @fact($x - 1) }
          }

the extended conditional is equivalent to but more readable, than the conditional expression:

          ($x <= 0 ? 1 : $x * @fact($x - 1))

Notice however that, despite the syntax, this if is definitively NOT the action described in conditional action: the branches of this are an extended expression, not a sequence of actions.

Remark that the @fact function is defined by recursion: the definition of @fact calls @fact itself.

Because Max/PD messages are included in extended expressions, they can be used to trace (and debug) functions:

          @fun_def fact($x)
          {
              print "call fact(" $x ")"
              if ($x <= 0)
              { 
                  print "return 1"
                  return 1 
              }
              else
              { 
                  @local $ret
                  $ret := $x * @fact($x - 1)
                  print "return " $ret
                  return $ret
              }
          }

(but see the predefined functions @Tracing and @UnTracing below for easier tracing of function calls). Assignments of global variables, Messages and aborts are instantaneous actions that can be used in extended expressions. They are used for the side-effect they achieve. In an extended expression, such actions cannot specify attributes.

A loop expression can be used to compute the factorial in an iterative manner, instead of a recursive one:

          @fun_def fact_iterative($x)
          {
              @local $i, $ret
              $ret := 1
              $i := 1
              Loop {
                  $ret := $ret * $i
                  $i := $i + 1
              } until ($i == $x + 1)
              return $ret
          }

which can also be written

          @fun_def fact_iterative_bis($x)
          {
              @local $i, $ret
              $ret := 1
              $i := 1
              Loop {
                  $ret := $ret * $i
                  $i := $i + 1
              } during [$x #]
              return $ret
          }

Again, the Loop construction involved here is an expression, not an action. So, one cannot specify a period (expression are supposed to evaluate in the instant), they cannot have attribute, and and end clause is mandatory to specify the number of iterations of the loop (but it cannot be a a duration).

Function’s Local Variables and Assignations

To factorize common sub-expressions, and then to avoid re-computation of the same expressions, extended expressions may introduce local variables using the keyword @local. This syntax mimics the syntax used for local variables in compound actions (group, whenever, etc.), but local variables in functions are distinct from the local variables in actions:

As a result, their implementation is optimized (for example, we know that these variables cannot appear in the clause of a whenever so the run-time does not need to monitor their assignments, they do not have a history, etc.). The cost of accessing a function's local variable is the same as accessing a function argument. Comparing to a global or local variable in groups the gain in the memory footprint and in housekeeping the environment is noticeable.

Local variables are introduced using the keyword @local in the first statement of an extended expression. Every variable that appears in the left-hand-side of an assignment and whose name does not appear in a clause is assumed to be a global variable1.

Extended expressions can be nested (through if, loop, forall and switch expressions). Each of these nested extended expressions may introduce their own local variables. A variable local to an extended expression is visible only to the sub-expression of this extended expression.

The initial value of a local variable is <undef>. Then, the value referred by a local variable is the last value assigned to this variable during the evaluation process. For example, with the definition

          @fun_def f($x)
          {
              @local $y
              $y := $x * $x
              $y := $y * $y
              return $y + 1
          }

the application of @f to x will compute x^4+1. Notice that the value of a local variable assignment, is, as for any assignment, the exe '0. So:

          @fun_def g($x)
          {
              @local $y
              $y := 2*$x
              $y := $y + 1
          }

will return '0 when called with x. See section on exec values.

Notice, the right hand side of a function's local variable assignment is an expression, not an extended expression.

The return Statement

The value of an extended expression is the value of the last return encountered during the evaluation. The return is not necessarily the last statement of the sequence. If there is no return in the extended expression, the returned value is the value of the last expression in the sequence. If there are multiple return at the same expression level, a warning is issued and only the last one is taken into account.

For example:

          @fun_def @print($x)
          {
              print $x
          }

When applied to a value, this function will send the print message that would eventually output the argument on the Max or PD console. We will see below that the value returned by sending a Max message is the exec '0.

A common pitfall

A confusing point is that, contrary to some programming language, return is NOT a control structure: it indicates the value returned by the nearest enclosing extended expression, not the value returned by the whole function. Thus:

          @fun_def pitfall($x)
          {
              if ($x) { return 0 }
              return 1
          }

is a function that always returns 1. As a matter of fact, the return 0 is the indication of the value returned by the branch of the if, not the value returned by the body of the function. However, function

          @fun_def work_as_expected($x)
          {
              if ($x)
              { return 0 }
              else
              { return 1 }
          }

returns 0 or 1 as expected, following the value of the argument $x, simply because the value of the function body is the value returned by the if expression which is the value returned by the function (the if is the last (and only) statement of the function body).

Extended Conditional Expressions and Iteration Expressions

Extended expressions enrich expressions using four constructs that mimic some action: loop, forall, if and switch. The keywords used are the same used to specify the corresponding actions. But the constructions described here are expressions, not actions:

These expressions are qualified as pseudo-actions. They have been introduced in extended expressions because loops can be used to specify iterative expressions, and conditionals are useful for controlling the flow of an expression's evaluation.

The Extended Expression If

The expression mimics the action If but its branches are extended expressions and it is not possible to define a label or the other attributes of an action. This construction is equivalent to the conditional expression

          (cond ? exp_if_true : exp_if_false)

So it is mainly used because it improves readability (the branches are extended expressions and may introduce their own local variables). The else branch is optional. If cond evaluates to false and the branch is missing, the returned value is undef.

The Extended Expression Switch

The syntax of the switch expression follows mutatis mutandis the syntax of the switch action. For instance, the Fibonacci recursive function can be defined by:

          @fun_def fibonacci($x)
          {
              switch ($x)
              {
                case 0: return 1
                case 1: return 1
                case @<(1):
                   @local $x1, $x2
                   $x1 := $x - 1
                   $x2 := $x1 - 1
                   return @fibonacci($x1) + @fibonacci($x2)
              }
          }

Recall that there are two forms of the switch construction. In this example, we use the form that compares the selector to the values ::antescofo 0 and 1. The third value is a predicate2 which is applied to the selector, and if true, the attached expression (here an extended expression) is evaluated. Here, for the sake of the example, the switchexpression handles the integer 0, 1 and all integers strictly greater than 1.

The other form of switch does not rely on a selector: the expression after the case is evaluated and if true, the corresponding expression (or extended expression) is evaluated.

The value of the switch expression is the value of the expression attached to the selected case. If there is no matching case, the value returned by the is undef.

The Extended Expression Loop

The Loop expression mimics the action construction, but, as for the other pseudo-actions, there are no delays or attributes. Furthermore, a loop expression has no period (because it is supposed to have zero-duration), and it does not accept the during clause with a relative or an absolute time: only a logical time, corresponding to an iteration count, is accepted in a during clause.

The value of a loop is undef. Thus, a Loop expression is used for its side effects. For example, the computation of the square root of a strictly positive number p can be computed iteratively using the Newton’s formula3: $$ x_0 = p, \quad x_{n+1} = \frac{1}{2}(x_n + \frac{p}{x_n}) $$ by the following function:

          @fun_def @square_root($p, $error)
          {
              @local $xn, $x, $cpt
              $cpt := 0
              $x := $p
              $xn :=  0.5 * ($x + 1)
              Loop
              {
                  $x := $xn
                  $cpt := $cpt + 1
                  $xn := 0.5 * ($x + $p/$x)
              } until (($cpt > 1000) || (@abs($xn -$x) < $error))

              if ($cpt > 1000)
              { print "Warning: square root max iteration exceeded" }

              return $xn
          }

We stress the fact that a return inside the loop is useless. As explained before, a return is not the indication of a non-local exit but the specification of the value returned by the nearest enclosing extended expression. A return in the loop body will specify the value of the body, which is then thrown away by the loop construct that always returns undef. This is why the exit of the loop is controlled here by an until clause and a return at the end of the function body is used to return the correct value.

The Extended Expression ForAll

This expression mimics the ForAll action. As an expression, it is used for its side-effect. The expression makes iteration possible over the elements of a tab, a map, or a range of integers. The return value is always undef.

Atomic Actions in Expressions

Some atomic actions, actions with zero-duration, are directly allowed in an extended expression: messages, abort and assigment to a global variable. Such actions may have neither a label nor other action attributes. The value of these actions in an extended expression is '0, that is, the value returned by the action-as-expression, see section Action As Expression.

Note that one can launch an arbitrary action within a function body, using the EXPR { ... } construction. This construction is a backdoor that can be used to “inject” arbitrary actions into the world of expressions4.

This possibility is not without danger because it introduces durative action into an instantaneous context. For example, it makes it possible to access a function's local variable that no longer exists:

          @fun_def pitfall2()
          {
              @local $x
              $x := 1
              _ := EXPR { 1 print $x }
              return $x
          }
          $res := @pitfall2()

will set the global variable $res to 1 but an error is signaled:

          Error: Vanished local variable or function arguments bad access at line ...
          Did you try to access an instantaneous variable from an action spanned in a function ?

Indeed, the action spanned in the function body happens one beat after the evaluation of the function call itself, which is instantaneous. So when the action is performed, the local variable corresponding to the call does not exist anymore.

Function Call Evaluation Strategy

Antescofo functions implement call-by-value strategy, but this must be tempered by the fact that data-structures are referred to through a pointer. See the side page Argument Passing Strategies. So, Antescofo functions can be impure (they can have side effects).

Argument evaluation order is not specified and is subject to change from one implementation of the language to the other.

And Antescofo functions are strict: all arguments are fully evaluated before evaluating the function body. (So, logical operators &&, || are not functions, they are specials forms.)

Functions as Values

In an expression, the @-identifier of a function denotes a functional value that can be used, for instance, as an argument of higher-order functions (see for example functions @map, @reduce, @scan, etc.). This value is of type intentional function.

The @-identifier of a function is not the only way to denote a functional value. The partial application of a function returns a function through a mechanism called currying, described in the next paragraph.

Curried Functions

In Antescofo, intentional functions can be partially applied. Partial function application says “if you fix the first arguments of the function, you get a function of the remaining arguments”. This notion is related to that of curried functions, introduced and developed by the mathematician Haskell Curry. The idea is seeing a function that takes n arguments as equivalent to a function that takes only 1 argument, with 0 < p < n, and that returns a function that takes n - 1 arguments5.

Consider for instance

          @fun_def @f($x, $y, $z) { $x + 2*$y + 3*$z }

This function takes 3 arguments, so

          @f(1, 2, 3)  

returns 14 computed as: 1 + 2*2 + 3*3. The idea of a curried function, or partial application, is that one can provide less than three arguments to the function @f. For example

          @f(11)

is a function still awaiting 2 arguments, y and z, to compute finally 11 + 2*y + 3*z. And function

          @f(11, 22)

is a function still awaiting one argument, z, to compute finally 55 + 33 z.

Curried functions are extremely useful as arguments of higher-order functions (i.e., functions taking other functions as arguments). An example has been given in the definition of @fibonacci to provide a predicate to the case.

For a more appealing example, consider the function @find(t, f) that returns the first index i such thaf(i, t[i]) is true. Suppose that we are looking for the first index whose associated value is greater than a. The value a will change during the program execution. Without relying on currying, one may write

          @global $a
          @fun_def @my_predicate($i, $v) { $v > $a }
          ...
          $t := ... ; somme tab computation
          $a := 3
          $i := @find($t, @my_predicate)

But this approach is cumbersome: one has to introduce a new global variable and must remember that the predicate works with a side effect and that the global variable $a must be set before using @my_predicate. Using partial application, the corresponding program is much simpler and does not make use of an additional global variable:

          @fun_def @my_pred($a, $i, $v) { $v > $a }
          ...
          $t := ... ; somme tab computation
          $i := @find($t, @my_pred(3))

The expression @my_pred(3) denotes a function awaiting two arguments i and v to compute v > 3, which is exactly what @find expects.

All user defined functions are implicitly curried and almost all predefined functions are curried. The exceptions are the special forms and overloaded predefined functions that take a flexible number of arguments, namely: @dump, @dumpvar, @flatten, @gnuplot, @is_prefix, @is_subsequence, @is_suffix, @normalize, @plot, @rplot, and @sort. When a predefined function does not support partial application, an error message is emitted when an incorrect application occurs.

Tracing Function Calls

It is possible to (un)trace the calls to a function during the program run with the two predefined functions: @Tracing and @UnTracing. The trace is emitted on Max or PD console (or on the output specified by the -–message option for the standalone).

The two predefined functions admit a variety of arguments:

Here is an example:

          @fun_def @fact($x) { if ($x < 1) { 1 } else { $x * @fact($x-1) } }
          _ := @Tracing(@fact)
          _ := @fact(4)  

which generates the following trace:

      +--> @fact($x=4)
      |    +--> @fact($x=3)
      |    |    +--> @fact($x=2)
      |    |    |    +--> @fact($x=1)
      |    |    |    |    +--> @fact($x=0)
      |    |    |    |    +<-- 1
      |    |    |    +<-- 1
      |    |    +<-- 2
      |    +<-- 6
      +<-- 24

Infix notation for function calls

A function call is usually written in prefix form:

          @drop($t, 1)
          @scramble($t)

It is possible to write function calls in infix form, as follows:

          $t.@drop(1)
          $t.@scramble()

The @ character is optional in the naming of a function in infix call, so we can also write:

          $t.drop(1)
          $t.scramble()

This syntax is reminiscent of the function/method call in SuperCollider. The general form is:

          arg₁ . @fct(arg₂, arg₃, ...)    ; or more simply
          arg₁ . fct(arg₂, arg₃, ...)  

The argᵢ are expressions. Notice that the infix call, with or without the @ in the function name, is not ambiguous with the notation exe.$x used to refer to a variable $x local in a compound action from the exe of this action, because the name of a function cannot start with the $ character.

The infix notation is less general than the prefix notation, because in the prefix notation, the function can be given by an expression. For example, functions can be stored into an array and then called following the result of an expression:

          $t := [@f, @g]
          ; ...
          ($t[$x])()

will call @f or @g following the value of the variable $x. This cannot be achieved with the infix syntax: only function names (with or without @) are accepted in the infix notation, not expressions. In addition, a function without arguments cannot be called in infix form.

The use of this notation will become apparent with the notion of method presented in chapter Actors.



  1. There is no chance that function would refer to a local varible introduced by a compound action. As a matter of fact, functions are defined at the top-level of the score, where only global variables are visible. Thus, only global and local variables introduced by the extended expression can be referred to in a function body. 

  2. Here the predicate is the partial application of @< to 1. The result is a function that compares its argument to 1. We use the infix notation of the relationnal operator < because partial applications are possible only on prefix notation (and @< is the prefix notation of the infix <). See the curried functions section in this chapter. 

  3. Note that there is a more efficient predefined function @sqrt

  4. In the reverse direction, it is possible to “inject” arbitrary expressions into the world of actions, using the _:= exp construction which allows for the evaluation of a simple arbitrary expression without other additional effects. 

  5. Sometimes a subtle distinction is made between currying and partial function applications. A curried function is a function of arity 1 eventually returning a function which is also curried (expecting one argument). In contrast, partial function application refers to the process of fixing a number p of arguments to a function of arity n, producing another function of smaller arity n - p