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:
def cxMvo = model:: mvo(sm_ret, sm_cov, sm_low, sm_high)
A description can be associated to a definition using the following syntax:
def cxMvo:"MVO Model" = model:: mvo(sm_ret, sm_cov, sm_low, sm_high)
Removing an existing definition is achieved with the undef command:
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:
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:
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:
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:
> 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:
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:
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:
-- 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:
def fact(n: int ) = iff(n <= 1, 1, [2..n].prod)
The above definition is non recursive. Recursive functions must declare their return type:
def recFact(n: int ): int =
if n <= 1 then 1 else n * recFact(n - 1)
You can use local variables when defining a function:
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:
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:
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:
-- 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:
def twice(x: real , f: real => real ) =
f(f(x))
This function can be called like this:
twice(1, sin)