This document walks through the syntactical elements of an Arrp program, and describes their semantics.
See also the Formal syntax specification. This document provides links into the formal specification whenever a syntactical element is introduced.
A module is the smallest unit of compilation, residing in a file or provided to the compiler through standard input.
A module consists of different kinds of declarations:
See Grammar: module.
module example; import lib; input iota :: int; external eta :: int > real64; gamma(x) = x + 1; main :: real64; main = lib.alpha(1) + eta(2)  gamma(iota);
The module name declaration is optional. If it is missing, the module gets the default name "m".
A module import declaration causes the imported module to be compiled, and the global names in that module become available in the current module.
The imported module must reside in a file named "modulename.arrp". The file must either be adjacent to the location of the importing module, or in one of the import directories given as an option to the compiler.
The names in the imported module are available in the importing module using qualified identifiers (see Grammar: qualifiedid):
The output of an Arrp program is the value of the global name "main", which must be present.
Inputs must be explicitly declared, along with their type (see Grammar: inputs).
In the following example, two inputs are declared: an integer named "a" and an array named "b" with 64bit floating point elements and two dimensions  the first infinite and the second of size 10:
input a :: int; input b :: [~,10]real64;
How input and output data is exchanged with the environment depends on the target form of compilation. See also the page on the C++ target.
Value declarations define values and functions and bind them to names:
pi = 3.14159265359; area_of_circle(r) = pi * r * r; area_of_right_triangle(a, b) = a * b / 2; a = area_of_right_triangle(10, 5);
The scope of globally declared names is the entire module. They are mutually recursive.
Names with scope local to an expression can be declared using the "let" and "where" expressions:
f(x) = let y = x * x in sin(y) + cos(y); g(x) = sin(y) + cos(y) where y = x * x;
See Grammar: letexpr, Grammar: whereexpr
A function is applied when followed by arguments in parenthesis (see Grammar: funcapplication).
Partial application is supported: a function applied to less arguments than it has parameters results in another function with the remaining parameters.
All functions are polymorphic: they can be applied to arguments of any type consistent with the expression of the function.
An unnamed function (a "lambda abstraction") can be defined as part of another expression:
f(g) = g(1,2) + g(3,4); a = f(\x, y > x * y);
See Grammar: lambda.
Occasionally, you may want to provide an explicit type declaration for a name:
y :: real64; y = 1 + f(x);
See Grammar: typedecl.
The type of a name can not be changed using a type declaration  the declared type must match the type of the name's expression.
Recursion is allowed for the purpose of recursive arrays. However, recursive use of functions is not supported. Some common uses of functional recursion can be achieved with recursive arrays. See Recursive arrays.
Due to a current limitation, the Arrp compiler requires a type annotation for at least one name in any recursive set of names.
An external function is declared with a name and a type, and it allows the name to be used as any other function, except that it is not polymorphic:
external transform :: [100]real64 > [50]real64; x = [100: i > sin(i/100)]; y = transform(x);
Precisely how an external function is executed depends on the target form of compilation. See also the page on the C++ target.
Arrays are a special kind of functions that map integers to values of some primitive type. Each formal parameter of such a function also called a dimension, and an actual parameter (argument) is also called an index. A multiparameter function is therefore a multidimensional array.
An array dimension ranges from 0 up to (and excluding) some number called array size. However, the size of one dimension can be infinite. Such an array is called a signal.
We represent a sequence of indices or array sizes for multiple dimensions with a tuple: <x,y,...>.
An array can be defined with an expression that specifies the array size and a list of mappings from indices to values (see Grammar: arrayfunc):
a = [5,10: x,0 > 0; x,y > x/y; ];
This is a 2dimensional array with size <5,10>, and it maps all indices where the second dimension equals 0 to the value 0, and all other indices of form <x,y> to the value x/y.
A signal (an infinite array) is defined by using ~
in place
of the array size:
s = [~,5: t,i > sin(t*i)];
Alternatively, an infinite array size is assumed when no size is given. Mind that only a single infinite dimension is allowed:
s = [t > sin(t)];
Each indexvalue mapping begins with an index pattern (see Grammar: indexmapping). Each pattern element is either a literal index value for which the pattern is valid, or a name of a variable representing any index value which can be used in the mapped expression.
A mapping is only effective for indices not matched by any pattern of the preceding mappings. Any index within the size of the array must be matched by at least one pattern.
An index mapping can be further split by index guards (see Grammar: indexmapping). An index guard is a boolean expression on array index variables. The mapped expression is effective only for indices where the expression is true. Guarded expressions must be followed by an expression without a guard that is effective for all indices not matched by any guard.
The following signal  defined using guards  has every third element equal to 1, and all others equal to 0:
s = [t  t % 3 == 0 > 1  0];
A guarded mapping is only effective for indices matched by its pattern, and not matched by guards and patterns of any preceding mappings.
An index guard must be a quasiaffine expression in index variables and constant integers. This means it can only contain:
Array definitions can be nested, resulting in an array with combined dimensions:
a = [t > [5: i > sin(t*i)]];
The array "a" is a 2dimensional array with size <t, 5>.
An array is applied when followed by indices in square brackets (see Grammar: arrayapplication):
a = [5,10: x,y > x+y]; b = a[3,4];
An array can be partially applied, just like an ordinary function, which results in an array with the remaining dimensions:
a = [5,10: x,y > x+y]; b = a[3];
A recursive array is defined using the name bound to the array expression inside the expression itself, or using the keyword "this":
a :: [~]real64; a = [0 > 0; t > a[t1] * 0.8]; b = [0 > 0; t > this[t1] * 0.8];
Multiple arrays can be mutually recursive:
a :: [~]real64; a = [0 > 0; t > b[t1] * 0.8]; b = [t > a[t] + 1];
An array can be defined by listing values of each of its elements (see Grammar: arrayenum):
x = 100; a = [4; x*3; [1; 2]; x+2];
Two arrays can be concatenated (see Grammar: arrayconcat):
a = [5: i > i] ++ [10: i > i^2];
Combined with recursion:
a :: [~]int; a = [0; 1] ++ [t > a[t] * 2];
Or shorter:
a :: [~]int; a = [0; 1] ++ a*2;
Arrp supports the following primitive types:
Literal values can be created for the following types: boolean, int, real64, complex64 (see Grammar: literal).
An expression can be converted to other primitive types (see Type Conversion).
Builtin operations and functions operate on primitive types. They can also be applied to arrays, pointwise.
The following tables list relations between argument types and result types, and additional conditions on argument types. In addition to type names, the following forms have special meanings:
a
: Any type. Two occurences in the same row signify the same type.a : type
: The type "a" must be a particular type or type class "type"real
: A type class comprising: real32, real64.complex
: A type class comprising types: complex32, complex64.simplenumeric
: A type class comprising: int, real.numeric
: A type class comprising: int, real, complex.If the types of arguments do not satisfy the conditions, argument promotion is attempted. Only the least number of promotions to satisfy the conditions are done. If there are multiple satisfiable sequences of promotions with equal length, the expression is ambiguous and the program is illdefined.
The following type promotions are attempted:
expression  result  conditions  comments 

!bool 
bool 

bool  bool 
bool 

bool && bool 
bool 

bool && bool 
bool 

a == a 
bool 

a != a 
bool 

a < a 
bool 
a : simplenumeric 

a <= a 
bool 
a : simplenumeric 

a > a 
bool 
a : simplenumeric 

a >= a 
bool 
a : simplenumeric 

a + a 
bool 
a : numeric 

a  a 
bool 
a : numeric 

a * a 
bool 
a : numeric 

a / a 
bool 
a : real or complex 
Floating point or complex division. 
a // a 
bool 
a : simplenumeric 
Integer division. 
int % int 
int 
Remainder.  
a ^ a 
a 
a : simplenumeric 
Exponentiation. 
expression  result  conditions  comments 

exp(a) 
a 
a : real or complex 

exp2(a) 
a 
a : int or real 

log(a) 
a 
a : real or complex 

log2(a) 
a 
a : real 

log10(a) 
a 
a : real or complex 

sqrt(a) 
a 
a : real or complex 

sin(a) 
a 
a : real or complex 

cos(a) 
a 
a : real or complex 

tan(a) 
a 
a : real or complex 

asin(a) 
a 
a : real or complex 

acos(a) 
a 
a : real or complex 

atan(a) 
a 
a : real or complex 

ceil(a) 
a 
a : int or real 

floor(a) 
a 
a : int or real 

abs(a) 
a 
a : int or real 

min(a,a) 
a 
a : int or real 

max(a,a) 
a 
a : int or real 
expression  result  conditions  comments 

real(complex32) 
real32 
Real part of complex number.  
real(complex64) 
real64 
Real part of complex number.  
imag(complex32) 
real32 
Imaginary part of complex number.  
imag(complex64) 
real64 
Imaginary part of complex number. 
expression  result  conditions  comments 

int(a) 
int 
a : simplenumeric 

real32(a) 
real32 
a : simplenumeric 

real64(a) 
real64 
a : simplenumeric 

complex32(a) 
complex32 
a : numeric 

complex64(a) 
complex64 
a : numeric 
When a primitive operation is applied to arrays, it is performed pointwise on array elements, given that the array sizes are compatible:
a = [5: i > i] + [5: i > 10*i];
Two array sizes are compatible if they are equal in all common dimensions. The result of the operation will have the size of the array with more dimensions.
If an argument has less dimensions than the result, the result at some index will use the value of the argument at an index equal in the common dimensions.
In addition, a primitive value is compatible with an array of any size, and will be used for all the result values.
For example, the array "a" can be multiplied with a constant value (b), with an array of equal size (c), or an array with more dimensions (d):
a = [t > sin(t*0.2)]; b = a * 0.1; c = a * [t > sin(t*0.01)]; d = a * [t > [10: i > sin(t*0.1/i)]];
An array or primitive value can be indexed with more indices than it has dimensions, resulting in the array value at an index with equal common dimensions.
For example, the function "f" can be applied to a primitive value (a), in which
case k[t]
is a constant expression. But it can also be applied to
a signal (b) so that k[t]
is variable:
f(k) = [t > sin(t * k[t])]; a = f(0.1); b = f([t > sin(t * 0.01)]);
Broadcasting also applies to individual mappings in an array definition. If the mapped values are arrays of different sizes, the result reuses values of arrays with smaller number of dimensions just like in a primitive operation:
a = [ 0 > 1; t > [10: i > t + i] ];