Definitions

Code definitions are formulas saved for future use. They are saved and loaded from any persistent storage used by AUSTRA. You can define either parameter-less definitions, that act like macros, or parametric definitions, which are the equivalent of user-defined functions.

Creating definitions

Definitions are created using the def statement:

Austra
def cxMvo = model::mvo(sm_ret, sm_cov, sm_low, sm_high)

A description can be associated to a definition using the following syntax:

Austra
def cxMvo:"MVO Model" = model::mvo(sm_ret, sm_cov, sm_low, sm_high)

Removing an existing definition is achieved with the undef command:

Austra
undef cxMvo

In the AUSTRA desktop application, definitions appear in the Variables panel, inside a Definitions node:

d 001

Definitions cannot use session variables

Code definitions must respect some limitations. The most important one is that they cannot reference session variables. This sequence of commands is invalid:

Austra
set vector = [1, 2, 3, 4];
def fact4 = vector.product -- Invalid code definition.

The reason behind this constraint is that session variables only store their current values, but not the formula that generated that value.

Definitions may use existing definitions

A code definition may refer to an existing definition. For instance:

Austra
def sm_cov = matrix::covariance(aapl, msft, hog, dax);
def sm_ret = [1, 0.9, 1.2, 0.8];
def cxMvo = model::mvo(sm_ret, sm_cov, vec(4), vec::ones(4))

In this case, removing either sm_cov or sm_ret, would also remove cxMvo.

Deterministic callings

Let's say we make this definition:

Austra
def extProduct = vec::random(4) ^ vec::random(4)

This definition calls twice a class method that creates a random vector. The caret operator, ^, combines those two vectors in a 4x4 matrix. Executing these definitions two times in a row gives, as expected, different results:

Austra
> extProduct
ans ∊ ℝ(4⨯4)
0.416065  0.493621  0.412334  0.0249965
0.390261  0.463007  0.386762  0.0234462
0.377909  0.448353   0.37452  0.0227041
 0.49103   0.58256  0.486626  0.0295002

> extProduct
ans ∊ ℝ(4⨯4)
0.0251534  0.0182728  0.0452763  0.00933612
0.0374942  0.0272379    0.06749   0.0139167
0.0555746  0.0403725   0.100035   0.0206275
0.0256057  0.0186015  0.0460906  0.00950403

That is the expected behaviour. However, this could be inconvenient to test properties of the result. For instance, we could want to check the determinant of the product, or that a double transpose works fine:

Austra
extProduct = expProduct''; -- Double transpose.
(extProduct * extProduct).det - extProduct.det^2

AUSTRA assumes that, inside a formula, all parameter-less definitions call must return the same value. For that purpose, the two above formulas are internally rewritten as:

Austra
let x = extProduct in x = x'';
let x = extProduct in (x * x).det - x.det ^ 2

A local variable is created under the hood for evaluating the definition just once inside the current formula.

This automatic caching only takes place for parameterless definitions. If you want to disable this behaviour, just add an exclamation sign right after the definition identifier, when using the definition:

Austra
-- This first expression returns true.
extProduct = extProduct;
-- This second expression returns false.
extProduct = extProduct!;
-- This expression also returns false.
extProduct! = extProduct

Function definitions

A definition can also have parameters, for defining a function. For instance, the factorial of an integer can be defined this way:

Austra
def fact(n: int) = iff(n <= 1, 1, [2..n].prod)

The above definition is non recursive. Recursive functions must declare their return type:

Austra
def recFact(n: int): int =
    if n <= 1 then 1 else n * recFact(n - 1)

You can use local variables when defining a function:

Austra
def mcd(a, b: int): int =
    let m = a % b in iff(m = 0, b, mcd(b, m))

And you can also define auxiliary functions inside a function definition:

Austra
def fact(n: int) =
    let f(n, acc: int): int = iff(n <= 1, acc, f(n - 1, n * acc)) in
        f(n, 1)

In this case, the inner function f is the one that is directly recursive. The outer function does not need to declare its return type.

Describing function definitions

A permanent description can be attached to a function definition using the same syntax as before:

Austra
def fact:"Iterative factorial"(n: int) =
    iff(n <= 1, 1, [2..n].prod)

The description will be serialized and saved in whichever data storage Austra uses.

Type names in AUSTRA

These are the types that can be explicitly used for parameters and return types in function definitions:

bool

The logical data type.

int

32-bit integers.

long

64-bit integers.

real

Double precision reals.

date

Austra dates.

string

Strings.

complex

Double precision complex values.

series

Time series.

matrix

Dense double precision matrices.

vec, cvec, ivec

Real, complex, and integer vectors.

seq, cseq, iseq

Real, complex, and integer sequences.

Arrays can be specified adding two brackets after a type name. Function types follows this convention:

Austra
-- A function that receives a real and returns a real:
real
=> real
-- Receives an integer and a vector, and returns a real:
int
=> vec => real

For instance, this definition allows to apply a function twice to an argument:

Austra
def twice(x: real, f: real => real) =
    f(f(x))

This function can be called like this:

Austra
twice(1, sin)

See Also