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
-
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
-
assert 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 (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:
-
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 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:
-
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
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 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 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:
- global variables
- local variables declared in the scope of an englobing compound action
- local variables declared in the scope of an englobing extended expression
- parameters of a function
- parameter of a process
- iterators introduced by a tab comprehension
- iterators introduced by a forall action
- 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
,
$x7
7.
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):
-
in named function defined with a
@fun_def
, free variable are necessarily global variable and they are handled by reference (i.e., as usual), -
in anonymous function defined by a lambda-expression
\$x.( ... )
, free variable are handled by copy and their assignment refers to the copy, not the original reference.
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.
-
@fun_def
is also used inside an actor definition to specify a (functionnal) methods. Methods specificities are adressed in chapter Actor. ↩ -
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. ↩
-
Here the predicate is the partial application of
@<
to1
. See the curried functions section in this chapter. 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<
). ↩ -
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. ↩ -
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. ↩ -
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. ↩