AUSTRA

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.

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:

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.

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.

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

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.

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.

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)`