Compilation (experimental)

Compilation is an experimental feature used for instance in differential curves. The purpose of compilation is to produce an equivalent but more efficient version of a given function. For that, a C++ code is produced, compiled and linked on-the-fly with the running antescofo instance. Only two kinds of functions are compiled:

Processes, including object's routines cannot be compiled.

Compilation can be quite effective. It can be also useless because the traduction of the Antescofo data representation into an equivalent C++ representation, which is necessary to call the compiled function, may have a certain cost that may cancel out the benefice of the compilation.

Compilation can be done implicitly by a differential curve. In this case the compilation is done when the score is loaded. The compilation of a function can also be done explicitly through a call to the function @compilation. This function takes only one argument which is the signature of the function to compile.


A signature is an antescofo value which represents the types associated to some entities in the language. These entities are functions and objects. This information is used during the compilation to produce the correct C++ code.

More precisely, a signature is a map that describe the type of the functions and methods that must be compiled. The keys in the map denote a function or an object, and the values specify the type assigned to the keys:

The methods that are compiled are the methods whose type is described in the signature of an object.

In the current version1, types are restricted to monormophic type. This severely restrict the compilation: we cannot compile polymorphic function. For instance, a function relying on @push_back cannot be compiled because @push_back is polymorphic: its first argument can be a tab or a nim.

There are no new entities introduced into the language to specify a type. Instead, we use a value that encodes this type. In the following, we describe these values.

Scalar Types

Scalar types are the types attributed to scalar values. They are encoded by a string:

type encoding example of a value of this type
undef "undef" or "void" <undef>
bool "bool" or "boolean" <undef>
int "int" or "integer" or "long" 3
float "float" or "double" or "numeric" 3.1415
string "string" "abc"
exe "exe" or "exec" or "obj" or "proc" they are no constant of this kind2

When several strings are given, they can be used interchangeably (they are aliases to refer to the same type).

The type of a tab

The type of a tab is specified by a tab with only one element: the representation of the type of the elements. Nota Bene that only homogeneous tab can be typed: there is no type corresponding to a tab containing elements of different types.

For instance, the type of a vector of floting point values is represented by TAB["float"] whch can also be written ["float"].

The function @typecheck can be used to check if a value is of a given type. So we can check for instance the type of a nested tab of boolean:

          @typecheck([["bool"]], [ [true, false], [false, true], [true, true] ])

must returns true because [["bool"]] describe a tab whose elements are of type ["bool"], i.e., tab of booleans.

The type of a function

The type of a functions is represented by a tab with two elements:

Here are some examples:

         int \times int \rightarrow float
         () \rightarrow bool
         bool \rightarrow bool
         bool \times bool \rightarrow bool
         float^n \times float^n \rightarrow float^n
[["int", "int"], "float"]
[[], "bool"]
[["bool"], "bool"]
[["bool", "bool"], "bool"]
[[ ["float"], ["float"] ], "bool"]

Notice that the type of a tab is a tab with only one elements whilst the type of a function is a tab which has two elements. So there is no ambiguity.

The type of an obj

The type of an obj is a ma whose keys are fields and methods of the obj. The type can be partial: it may describe only some fields and some methods, not all. By methods here we mean the function associated to the obj and defined by a @fun_def, not a routine.

Fields and methods are specified using a string representing their identifier. The value assoicated to the key is the type of the key. For instance:

          MAP { "$count" -> "int",
                "increase" -> [["int"], "int"],
                "decrease" -> [["int"], "int"]

can be used to type the object:

          @obj_def obj::counter()
                   @local $count := 0

                   @fun_def increase($p = 1) {
                          $count += $p
                          return $count

                   @fun_def decrease($p = 1) {
                          $count -= $p
                          return $count

The type of an object can be partial: only a subset of of the fields and of the methods can be described.

The Predicate @typecheck

The predicate @typecheck can be used to check if the first argument is a type compatible with the second argument. For instance:

          @typecheck("int", 3)      ; returns true
          @typecheck("int", true)   ; returns false
          @typecheck("zzz", true)   ; returns <undef> because "zzz" does not represent a type


A signature is not a type: it is an environment which gives the types of some Antescofo entities: variables, functions and objects. It is represented also by a map, as for the type of an object, but the key are different. The key in a a signature can be

For example, with the previous definition of obj::counter, the following code

          @fun_def @f($o, $p) { return $o.increase($p) }

          $sig := MAP { "$count" -> "int",
              "increase" -> [["int"], "int"],
              "decrease" -> [["int"], "int"]

          _ := @compilation(MAP { @f -> [["obj", "int"], "int"],
                                  obj::counter -> $sig })

can be used to compile the methods increase, decrease and the function @f.

Notice the type of @f: the first argument is an instance of obj::counter but this type does not exist. What exists, is a less precise type "obj" which is the type of an instance of an object, whatever it is. The type described by $sig is the type of the class obj::counter, not of an instance of this class.


In order to be compiled all expressions involved in the function evaluation must be compiled. In particular, it means that all the functions and methods) invoked must be compiled (and the constraint applies recursively). This is why a signature usually lists not only the type of the function to be compiled but also the type of all involved functions.

When a function @f is compiled, the compiler looks for a compiled version of all invoked functions @g. If the compiled version does not exist, it looks in the signature to find the type of @g. If this type is not found, an error is declared and the compilation stops.

A compiled version of a function @g exists if it has been compiled by a previous call to @compilation or because it has been already compiled in the current call or because @g is a predefined function that comes with a predefined compiled version.

If the compilation is successful:

This behavior is not homogeneous and is subject to change in the future.

Compilation's restrictions

We already mention that they are severe constraints restricting the set of compilable expressions:

We are working to extend the set of compilable expressions. Please reports your specific needs in the Antescofo user's forum.

Compilation Errors

The compilation workflow proceed as follows:

The compilation process is not stopped by an error on the compilation of a function, but tries to compile the other functions specified in the signature.

  1. the type system is subject to be enhanced in the future. 

  2. A value of type exe is created by process call or an actor instantiation, the launch of a group or a lopp, etc. They are not denotable, meaning that there is no constant of this type in the language, even if such value can be created as the result of the evaluation of some expressions.