Functions¶
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:
-
they are defined by an expression that specifies how the values provided as arguments in a function call are transformed into a(nother) value;
-
they are themselves values (see section Function As Value);
-
a function call can appear only inside an expression (as a sub-expression) or where an expression is expected (for example in the attributes of an action);
-
and the call of a function takes “no time” (see sect. synchrony hypothesis).
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
-
simple expressions optionally preceded by the
return
keyword -
local or global variable assignments using := (right hand side is a simple expression)
-
iteration expressions
Loop
andForAll
whose sub-expressions are extended expressions -
extended conditional expressions
if .. else ...
and … whose sub-expression are extended expressions -
Max/PD messages
-
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:
-
their lifetime is limited to one instant, the instant of the function call,
-
so it is neither necessary nor possible to refer to these variables outside of their definition scope (i.e, the nearest enclosing extended expression).
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:
-
their sub-expressions involve extended expressions and not sequences of actions,
-
their evaluation takes “no time” (they have zero-duration which is usually not the case of the corresponding actions),
-
they have no label,
-
they have no synchronization attributes,
-
they have no delays.
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 switch
expression 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:
-
no argument: all user-defined functions are traced/untraced.
-
the functions to trace/untrace: as in
@Trace(@in_between, "@fib")
, will trace/untrace the call and the returns to the listed functions. Notice that the function to (un)trace can specified with their name or via a string. -
a tab that contains the functions to (untrace through their name or through strings.
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.
-
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. ↩
-
Here the predicate is the partial application of
@<
to1
. The result is a function that compares its argument to1
. 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. ↩ -
Note that there is a more efficient predefined function @sqrt. ↩
-
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. ↩ -
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. ↩