Scalar Values¶
Antescofo offers a rich set of value types described in this chapter
and those that follow. The value types examined in this chapter —
undef
(the undefined value), bool
(booleans), int
(integers),
float
(double floating point values), fct
(intensional functions),
proc
(processes) and exec
(threads of execution) — are
indecomposable values. Functions and processes are also covered in
more depth respectively in chapters Function and Process. The value
types examined in the next chapter are data structures that act as
containers for other values.
The Undefined Value¶
There is only one value of type undef
. This value is written
<undef>
This value is the value of a variable before any assignment. It is
interpreted as the value false
if needed.
The undefined value is used in several other circumstances, for example as a return value for some exceptional cases in some predefined functions.
Symbol¶
A symbol is just a name, a simple identifier used for instance to denote the receiver of a message. Because reserved keywords are indistinguishable of symbols they are some constraints to use it beyond message receivers. A macro-expansion mechanism can also be used to substitute a symbol by a computed value. These advanced features are presented in teh Advanced Symbols chapter.
Boolean Values¶
There are two boolean values denoted by the two symbols true
and false
. Boolean values can be combined with the
usual operators:
-
the logical negation
!
("not") written prefix form1: for instance,! false
returnstrue
; -
the logical disjunction
||
("or") written in infix form: e.g.,$a || $b
; -
the logical conjunction
&&
("and") written in infix form: for example,$a && $b
.
Logical conjunction and disjunction are lazy: a && b
does not evaluate b
if a
is false
and a || b
does not evaluate b
if
a
is true
.
Integer Values¶
Integer values are written as expected. The arithmetic operators
+
,
-
,
*
,
/
,
and %
(modulo),
are the usual ones with the usual priority.
Integers and float values can be mixed in arithmetic operations and the
usual conversions apply. Similarly for the relational operators
<
,
<=
,
==
(equal),
!=
(not equal),
>=
,
and >
:
when an integer is compared against a float, it is first converted into
the equivalent float.
In the context of a boolean expression, a zero is the false value and
all other integers are considered to be true
.
Float Values¶
As in the C language, Float ("floating point") values, a data type that can store a long decimal number, are handled as IEEE doubles. The arithmetic operators, their priority and the usual conversions apply. Beware that two floats may be printed in the same way but may differs from a very small amount.
Float values can be implicitly converted into a boolean, using the same rule as that of integers.
For the moment, there is only a limited set of predefined Mathematical Functions @abs, @acos, @asin, @atan, @atan2, @between, @bit_and, @bit_or, @bit_shiftl, @bit_shiftr, @ceil, @clear, @cos, @cosh, @exp, @floor, @knn_create, @knn_rebuild, @knn_delete, @knn_rnd, @knn_search, @knn_rsearch, @knn_scan, @knn_rscan, @knn_combine, @knn_rcombine, @log, @log10, @log2, @max, @min, @ode_solve, @pow, @rand, @rand_int, @random, @rnd_bernoulli, @rnd_binomial, @rnd_exponential, @rnd_gamma, @rnd_geometric, @rnd_normal, @rnd_uniform_float, @rnd_uniform_int, @round, @sin, @sinh, @sqrt, @tan, @y0, @y1 .
These functions correspond to the usual IEEE mathematical functions. They also accept integers.
Functional Values¶
An intentional function is a value that can be applied to arguments to achieve a function call. Like others kinds of values, it can be assigned to a variable or passed as an argument in a function or a procedure call.
Intentional functions f are defined by rules (i.e. by an expression)
that specify how an image f(x) is associated to an element x.
Intentional functions can be defined and bound to an @-identifier using
the construct @fun_def
introduced in chapter
Functions. In an expression, the @-identifier of a function denotes
the corresponding functional value. Intentional functions can also be
defined through
lambda-expression. Lambda-expressions
are expressions that may appear where an expression is
expected. Functional value can also be built by partial application of
a function.
Functional values can be used for instance as an argument of higher-order functions (see examples of higher-order predefined function in section map for map building and map transformations).
Looking at functions as values is customary in functional languages like Lisp or ML. Lambda-expressions offer the same expressivity; for instance, they can be nested and they can refer to variables defined outside the lambda-expression, whih require a notion of closure. Antescofo's closure is done by value. Named functions in Antescofo are more simple: they cannot be defined in a nested way, only from the top level, as in the C language.
Some intentional functions are predefined and available in the initial environment like the IEEE mathematical functions. See annex Library for a description of predefined functions.
There is no difference between predefined intentional functions and
user’s defined intentional functions except for those in a Boolean
expression, where a user’s defined intentional function is evaluated to
true
and a predefined intentional function is evaluated
to false
. See also the predicates @is_fct and
@is_function.
Intentional functions are described more in detail in chapter Functions.
Intentional functions differ from extensional functions. Extensional functions h are defined by explicitly enumerating the pairs (x, h(x)) defining the function. Maps and NIMs are example of extensional functions and they are described in the next chapters.
Proc Values¶
Processes are for actions what functions are for expressions. And in the same way that functions are values, processes are values too.
The ::-name of a process can be used in an expression to denote the
corresponding process definition, in a manner similar to the
@-identifier used for intensional functions. Such values are qualified
as proc values and the corresponding type is named proc
. Like
intentional functions, proc values are first class values. They can be
passed as arguments to a function or a procedure call.
They are two main operations on proc values :
-
“calling" the corresponding process;
-
“killing” all instances of this process.
Processes are described more in detail in chapter process.
Exec Value¶
An exec value, also called exe, refers to a specific run of a compound action. Such values are created when a process is instantiated, see section process, but also when the body of a loop, a forall, or of a whenever is spanned. This value can be used to abort the corresponding action. It is also used to access the values of the local variables of this action. See below.
They are several ways to get an exec:
-
The special variable
$MYSELF
always refers to the exec of the enclosing compound action. -
The special variable
$THISOBJ
always refers to the object referenced by a method (in a method definition) or in the clauses of an object definition. -
A process call returns the exec of the instance launched, see section process call.
-
Through the “action as expression” construct ⎯ see section Action As Expression.
-
Using the function @exe_parent.
Exec as Coroutines or Lightweight Processes¶
An exec refers to a durative action (an action that lasts through time). They corresponds to the notion of shred in ChucK or more fundamentally, to the notion of coroutine used to structure and implement the reactive and timed part of the runtime.
The concept of coroutines was introduced in the early 1960s and constitutes one of the oldest proposals of a general control abstraction. The notion of coroutine was never precisely defined, but three fundamental characteristics of a coroutine are widely acknowledged:
-
the values of data local to a coroutine persist between successive calls;
-
the execution of a coroutine is suspended as control leaves it, only to carry on where it left off when control re-enters the coroutine at some later stage;
-
they are non-nonpreemptive: coroutines transfer control among themselves in an explicit way with some control-transfer operations (there is no preemption, nor interruption).
As coroutines, Antescofo's execs have several characteristic features: they are first-class objects, which can be freely manipulated by the programmer. There is only one control transfer operation: waiting a delay, see chapter The Manufacturing of Time. This operation corresponds to the yield operation used to suspend a coroutine execution: the coroutine’s continuation point is saved so that the next time the coroutine is resumed, its execution will continue from the exact point where it suspended. But here there is no explicit resume operation for execs. And contrary to usual coroutines, the creation of new coroutines corresponds to the principal control structures (whenever, loop, curve... implicitly each compound action specifies a coroutine).
An exec, much like a thread, represents an independent unit of execution which operates concurrently and can share data with other execs. But unlike conventional threads, whose execution is interleaved in a non-deterministic manner by a preemptive scheduler, an exec is a deterministic piece of computation and is naturally synchronized with all other execs via the shared timing mechanism, the synchronization constructs and the priority of actions.
Execs have a priority, so the sequence of execution of execs that run at the same date (in the same instant), is unambiguously determined. See chapters The Manufacturing of Time and Action Priority.
Alive and Dead Exec¶
The action run referred by the exec may be elapsed or still running. In the former case we say that the exec is dead and active in the latter case. For example, the exec returned by evaluating an atomic action (with a 0-duration run) returns the special exec which is always dead.
A conditional construct can be used to check the status of the exec:
$p := ::proc(...)
...
if ($p)
{ /* performed if the instance of ::proc is still running */ }
else
{ /* performed if the exe is dead */ }
Abort with Exec¶
Exec values can be used as an argument of an abort
command. Notice that an exec refers to a specific instance of
an action. So used in an abort command, it aborts solely the referred
instance while using the label of an action will abort all the running
instances of this action.
An abort command on a dead exec does nothing (and does not signal an error).
The hierarchy of execs¶
The hierarchy of live exec can be queried using the predicate @exe_child_of which returns true if its first argument is a descendant of the second (by convention, an exec descends of itself). This predicate can be used for instance to check if a process has been launched by another one.
Some exec have no ancestors. This is the case for instance for the implicit groups that contains the actions anchored to a musical events. Such exec are called top-level.
Accessing a Local Variable Through an exec.¶
Exec can also be used to access the local variables of the referred compound action. This is mostly useful for processes (cf. sect. Assignment using the dot notation).
Accessing a local variable through an exec relies on the dot
notation: the left hand side of the infix operator .
must be an expression referring to an active exec and the right hand
side is a variable visible from the referred exec.
Accessing a variable through the dot notation is a dynamic mechanism. The variable is looked first in the instance e referred by the exec, but if not found in this context, the variable is looked up in the context of the parent exec (i.e., in the exec of the enclosing compound action that has launched e), and so on, traversing the hierarchy of exec until it is found. If the top-level context is reached without finding the variable, an undef value is returned and an error message is issued. See sect. procVariable for an example.
The reference of a local variable using the dot notation can be used in an assignment, see sect. procVariable for an example involving a process instance (but this feature works for any exec).
-
The character “
!
” can be the first letter of a symbol. So it is wise to leave a blank space between the logical operator and its argument. ↩