They are two ways to define an intentional function in Antescofo : using the @fun_def construction or using a lambda expression. Functions of the first kind are called named functions while the second kind corresponds to anonymous functions. Both rely on extended expressions for their definition.


Named function definition

The first construction defines a named function at the top-level of the score1. The function identifiers begins with @ and can be used elsewhere to refer to the function. The definition of the function occurs when the score is loaded, before any computation.

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 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   }  

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.

The second example, function @beat2ms, is not a pure function (i.e., a function in the mathematical sense) because when called at two different moments, with the same arguments, the returned value may be different (since the value of the variable $RT_TEMPO may have changed).


The syntax of function definition with a @fun_def declaration is given below. We insist that the declaration is not an expression and processed when the score is loaded.

The body of an intentional function is defined by an extended expression which is more expressive and convenient than simple expressions. In the rest of this chapter, we present the definition of nammed functions, the extended expressions that can be used to define user's function, and the definition of anonymous functions through lambda-expression. The final paragraph compars named and unamed functions and their definition.


Extended expressions

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.

An extended expression is an optional local variables 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

  7. assert actions

This structure is formalized by the diagram below where


Rationale of Extended Expressions. An extended expression is allowed only in the body of a (named or unamed) 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 instantaneous) 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 variable2.

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
          }

A local variable can be initialized directly in the @local declaration, so the previous definition can be rewritten:

          @fun_def f($x)
          {
              @local $y := $x * $x
              $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 := 2*$x
              $y := $y + 1
          }

will always returns '0. See section on exec values.

Extended expressions can be nested (through if, loop, forall and switch expressions). But the right hand side of a function's local variable assignment is an expression, not an extended expression.

Each extended expression introduces its own scope and a variable that is local to an extended expression is visible only to the sub-expressions of this 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 0 and 1. The third value is a predicate3 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 formula4: $$ 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 $x := $p,
                     $xn := 0.5 * ($x + 1),
                     $cpt := 0
              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, assert, 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 expressions5.

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 := 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.


Lambda-expression

A lambda-expression can be used to define an anonymous function anywhere an expression is expected. For instance, the previous function @midi2hz can be defined by the following expression:

        $midi2hz := \ $midi . (  440.0 * exp(($midi-69) * log(2) / 12) )

The right hand side of the assignment is a lambda-expression. The general form of a lambda-expression is

        \ $arg1, $arg2, ... . ( extended expression )

The evaluation of a lambda-expression returns an anonymous function. This functional value is also called a lambda-value. In the previous example, we assign the lambda-value to the variable $midi2hz. This reference to the anonymous function can latter be applied:

        $pitch1 := $midi2hz(62)
        $pitch2 := @midi2hz(62)
        @assert $pitch1 == $pitch2

The two applications must return two equivalent results: for any x, the application of $midi2hz and the application of @midi2hz return the same result6.

Beware that assigning a function to a variable has nothing to do with a lambda-expression. One can assign a variable with a function defined through a @fun_def:

        $midi2hz := @midi2hz

On one hand, the @fun_def declaration bind a functional value to the name @midi2hz. And the binding mechanism is not an expression. On the other hand, the lambda-expression \$midi.(440.0 *exp(($midi-69) * log(2) / 12)) is an expression and its evaluation returns a functional value equivalent to @midi2hz. The assignation of a variable is used to refer later to this functional value.

First Examples

Lambda-expressions are heavily used in functional languages like Lisp or ML. They are convenient to provide arguments to higher-order function. For instance, the function @find returns the index of the first element of that tab that satisfies a predicate. This predicates takes two arguments: the first is the index of the element to check and the second is its value. So, finding if a value 5 is in tab $t can be computed by:

        $t.find( \$v.($v==5) )

Lambda-expressions can be nested, which allows the definition of functions returning a function. This is usefull to write generic function that are ‟progressively” specialized through function application. Consider for instance:

       $f := \$x.(\$y.($x + $y))

the functional value assigned to $f is the result of a lambda-expression whose body is a lambda-expression, i.e. it is a function returning a function. When $f is applied to an argument x, the result is a function awaiting one argument y to return x+y.

        $f0 := $f(0)
        $f1 := $f(1)
        $f2 := $f(2)

        $t := [ [$f0($i), $f1($i), $f2($i)] | $i in (4) ]

        @assert $t == [ [0, 1, 2],
                        [1, 2, 3],
                        [2, 3, 4],
                        [3, 4, 5] ] 

Free variables in a lambda expression

In the previou example, the variable $x that appears in the nested lambda-expression f = \$y.($x + $y), is called a free variable because it is not introduced by the lambda expression f: when f is considered in isolation, $x appears as a reference specified outside f, in the context of the definition. Variable $y is not a free variable of f because it is introduced as a parameter of the function. Alternatively, we say that $y is bound by f and that $x is captured by f.

The term free variable refers to the variable used in the body of a function that are neither local variables nor parameters of that function. In Antescofo, free variables can be:

  1. global variables
  2. local variables declared in the scope of an englobing compound action
  3. local variables declared in the scope of an englobing extended expression
  4. parameters of a function
  5. parameter of a process
  6. iterators introduced by a tab comprehension
  7. iterators introduced by a forall action
  8. iterators introduced by a forall expression

The following code fragment gives an example where variable $x illustrate item i above for the functions referred by $f:

        $x1 := 1
        Group G
        {
             @local $x2 := 2
             @local $t

             forall $x7 in [1, 3, 5, 7, 11, 13, 17, 19, 23]
             {
                  $t := [ \$x4.( @local
                                    $x3 := 3,
                                    $f := \$z.( $z*($x7 + $x6 + $x4 + $x3 + $x2 + $x1))
                                    return $f(0)
                               )
                        | $x6 in ($x7) ]
             }
        }

This contrived example does not compute anything meaningfull: it is just used to outline that the free variables of the lambda-expression assigned to $f are $x1, $x2, $x3, $x4, $x6, $x77.

Free variable in a @fun_def. The free variables of a function defined by a @fun_def are necessarily global variables because these functions are defined only at top-level and cannot be nested in another scope. So the only possible reference to the context is through a global variable.

Closure of free variables

When a function is defined, one has to decide what to do with the free variables involved in the function definition: when to evaluate these variables and how to access it. The corresponding mechanism, called a closure, associates a value to a free variable and controls the assignment to this variable.

In Antescofo, the closure mechanism depends on the kind of functions (global or not-global):

  named function anonymous function
occurrence in an expression refers the global variable refers a copy initialized at definition time
left-hand-side of an assignment refers the global variable assign the local copy

In named functions, free global variables are managed by reference, that is, as usual: the evaluation of a variable query the value of the variable at the time of evaluation. And the assignment of a global variable is done at the time of evaluation.

When a lambda-expression is evaluated to build a functional value, free variables are simply replaced by a references to local variables initialized by the value of the free variables.

Examples of closure

The code fragment

          $a := 0
          $f := \$x.($x+$a)
          print ($f(0))
          $a := 33
          print ($f(0))

will print 0 and then 0 because free variable $a is managed by copy.

This contrast with the handling of global variable in named function:

          @fun_def f($x) { $x+$a }
          print ($f(0))
          $a := 33
          print ($f(0))

will print 0 and then 0 because the free variable $a is a reference to the global variable.

In anonymous functions, all free variables are managed by copy:

          $a := 0
          Group G
          {
               @local $a := 1
               $f := \$x.($x+$a)
               print A ($f(0))
               $a := 33
               print B ($f(0))
          }
          print C ($f(0))
          $a := 44
          print D ($f(0))

will print A 1 , B 1 , C 1 and D 1. As a matter of fact, the free variable $a in the lambda-expression refers to the local variable introduced by group G (the global variable $a is hidden by the local variable in the scope of G). When the lambda-expression the free variables are replaced by variables local to the function body. These local variables are initialized by the current value of the referred variable, that is 1.

Consider the following code:

        $a := 0
    Group G {
          @local $b := 1
              $f := \$x.( $a := $x
                      $b := $x  )
          _ := $f(11)
          print $a $b
    }
    _ := $f(22)
    print $a $b

Its evaluation will print 0 1 and then 1<undef>: they closure of the free variables is made by copy, so the assignments in the body of the lambda-expression leave these variables intact. In the second printing, variable b has not been declared so it is global and its value is <undef>.

Rationale of Antescofo's closures

Named and anonymous functions handle their closure differently because the closure of a named function is very simple: it consists only of global variables. These variables have a lifetime that corresponds to the whole program execution, so they are always accessible. Enabling the assignment of global variables from a named function is then always possible. Such assignment is called a side effects: It makes possible for a function call to to affect the rest of the program other than by the returned value. That said, using side effects is usually considered as a bad practice.

The closure of anonymous functions are more complex: free variables corresponds to a large variety of references, Cf. free variables in a lambda

Such references raises a difficulty: functional values escape easily their scope of definition so the lifetime of the free variables involved by the function definition may not intersect the lifetime of the function call. Consider for instance:

       $f :=  \$x.(\$y.($x := $y))

In the nested lambda-expression below, the free variable $x is a reference to the argument of the outer lambda. Then, what would be the result of

         $f1 := $f(1)
         $f2 := $f1(2)

the value of $f1 is a function which await a value y to evaluate $x := y. But, when $f1 is applied, to which object refers $x? the application of the outer lambda does not exist anymore, only its result under the form of the functional value referred by $f1.

Applicative languages (i.e., functional language with imperative variables like OCAML) specify that the original variable must persist as long as someone may access it. This leads to the use of a garbage collector to manage the memory used for these variables.

Antescofo avoids the use of a garbage collector for handling the variables, because of the real-time constraints. Variable life-time is restricted to their scope of definition, which entails a much more efficient management. Hence, free variables in a lambda-expression become variables local to this expression and they are initialized at their creation with the value of the referred variables.

More Examples: Combinators and Anonymous Recusive Functions

The following examples are more formal exercises on lambda-expression manipulations but they have been studied extensively for their theoretical properties.

It is possible to define recursive functions without naming the function. The idea is to introduce an additional argument that is used for the recursive call and to provide the right value for this argument:

          $fact := \$f.( \$x.(   if ($x <= 1)
                                 { return 1 }
                                 else
                                 { return $x * $f($f)($x-1) }  ) )

          $factorial := $fact($fact)

Once $fact has been applied to give $factorial, the referred value is captured and the variable $fact can be assigned to another value. The trace of the evaluation of $factorial(2) is the following (we use the variable $fact to refer to the functional value).

        $factorial(2) == $fact($fact)(2)
                      == if (2 <= 1) then { return 1 } else { return 2 * $fact($fact)(1) }
                      == 2 * $fact($fact)(1)
                      == 2 * (if (1 <= 1) then { return 1 } else { return 2 * $fact($fact)(0) })
                      == 2 * 1
                      == 2

The application of $fact to itself can be grasped by a fixed-point combinator fix that satisfies fix(f) = f(fix(f)) meaning that if g is the fixed-point of f, then g = f(g) or, in other word, g(x) = f(g(x)) for all x.

They are many fixed-point combinators. One possible definition is the combinator called Y:

        $Y :=  \$f.( \$x.($x($x))
                         (\$y.($f (\$z.( ($y ($y))($z))))) )

One can check that Y g = g(Y g). Using combinator Y, one can program factorial and Fibonacci functions as

        $facto := \$f.(\$x.(($x <= 1 ? 1 : $x * $f($x - 1))))
        $factorial := $Y($facto)

        $fibo := \$f.(\$x.(($x < 2 ? 1 : $f($x - 1) + $f($x - 2))))
        $fibonacci = $Y($fibo)


Comparison of Named and Anonymous Functions

The following table recapitulates the difference between named functions defined using @fun_def and functions defined through lambda-expressions.

named function anonymous function
location of a definition at the top-level of the score everywhere an expression is allowed
nested definition no yes
function's body extended expression extended expression
free variable in an expression refers to a global variable refers a local variable initialized at definition time
assignment of the free variable assignment of the global variable assignment of the local variable
partial application yes yes
higher-order function yes yes
functional values are first class value yes yes
evaluation strategy call by value call by value
order of arguments evaluation unspecified unspecified
named arguments yes yes
default argument value yes no
trace of function call yes no


Evaluation strategy, partial application, named parameters, default argument valueare detail in the next section.


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. Lambda-expressions are also another means to denote a functional value. The partial application, described in next section, is a also a way to build a new functional value through a mechanism called currying.



  1. @fun_def is also used inside an actor definition to specify a (functionnal) methods. Methods specificities are adressed in chapter Actor

  2. There is no chance that function would refer to a local variable introduced by a compound action. As a matter of fact, named 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. 

  3. Here the predicate is the partial application of @< to 1. See the curried functions section in this chapter. 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 <). 

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

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

  6. But, although equivalent, these two functional values are not equal in Antescofo's == sense. As a matetr of fact, deciding the equivalence of two intentional functions is not computable in the general case. As a consequence, the check @assert $midi2hz == @midi2hz will fail. Equality for functional values checks that the two arguments are defined by the same definition in the score. 

  7. The case 5 is missing but the previous code fragment can be embeded in the body of a process and one parameter of the process can appear in the previous list. A similar remark apply for case 8