Function definitions¶
Functions take a list of arguments and evaluate the expression in their body, producing a return value. ABS functions are always pure. This means the body of a function can use all pure expressions but no expressions with side effects.
Functions can be parametric, which means that they can take and return
parametric datatypes. This means that a function head defined over a
parametric list datatype can return the first element of a list, regardless of
its type. Parametric functions are defined like normal functions but have an
additional type parameter section inside angle brackets (< >) after the
function name.
FunctionDecl ::= |
|
[ |
|
|
|
|
Example:
def Rat abs(Rat x) = if x > 0 then x else -x; ①
def Int length<A>(List<A> list) = ②
case list {
Nil => 0
| Cons(_, ls) => 1 + length(ls)
};
def A head<A>(List<A> list) = ③
case list { Cons(x, _) => x };
Partial function definitions¶
For reasons of simplicity and analyzability, ABS does not offer
higher-order functions. On the other hand, many common patterns of
functional programming are extremely useful, for example the
well-known map, filter and fold higher-order functions.
For this reason, ABS supports partial function definitions.
Partial function definitions are function definitions taking an additional set of parameters. These additional parameters can be either names of normal functions, or anonymous functions (see Anonymous functions). Partial function definitions define a set of functions which only differ in function applications but share overall structure. Put another way, partial function definitions define second-order functions – functions that take first-order functions as arguments. Partially defined functions can be used inside functional code, but cannot be passed as parameters to other partial functions.
A partially defined function is called the same way as a normal function, with a separate, non-empty argument list containing the functional arguments. For recursion inside the body of a partially defined function, omit the function parameter list.
PartialFunctionDecl ::= |
|
[ |
|
|
|
|
|
|
Example:
// Apply a function fn A -> B to a value A
def B apply<A, B>(fn)(A value) = fn(value);
def Int double(Int x) = x * 2;
{
Int doubled = apply(double)(2); ①
}
doubled will have the value four.Example:
def List<B> map<A, B>(f)(List<A> list) = case list { ①
Nil => Nil
| Cons(x, xs) => Cons(f(x), map(xs)) ②
};
def Int double(Int x) = x * 2;
{
// doubled = [2, 4, 6]
List<Int> doubled = map(double)(list[1, 2, 3]);
}
Note
For each call of a partial function, a normal function definition is generated at compile time by replacing the functional parameters syntactically by the functions passed in the additional parameter list. This is done before type checking and after delta and trait flattening – any type mismatch and similar errors are caught afterwards during type checking. If multiple statements call a partially defined function with the same function-name arguments, only one expansion is generated.
Anonymous functions¶
To reduce the need to declare a function with a new function name explicitly every time a partially defined function is called, ABS uses anonymous functions. Anonymous functions are only allowed in the first arguments list calls to partially defined functions.
AnonymousFunction ::= |
|
|
An anonymous function specifies a number of parameters and an expression that may refer to the declared parameters.
The following example is equivalent to the previous example, but does not
define the double function explicitly:
{
List<Int> list = list[1, 2, 3];
list = map((Int y) => y * 2)(list); ①
}
list will have the value list[2, 4, 6]Anonymous functions can refer to variables and fields accessible in the context of the partial function call:
{
Int factor = 5;
List<Int> list = list[1, 2, 3];
list = map((Int y) => y * factor)(list); ①
}
list will have the value list[5, 10, 15]Note
Anonymous functions are inlined into the expansion of the partial function definition. Errors caused by wrong typing are caught after the expansion during the type checking of core ABS, but the expanded function definition has an annotation referring to the statement that caused the expansion, hence error reporting will be accurate wrt. the original source code.
Built-in functions¶
Some special “functions” cannot be defined with pure expressions, for
example the function println. The definition of such functions is
done via a special function body written as builtin, with optional
pure expression arguments builtin(a, "b", 3). Such builtin
functions have to be defined separately in the code generator for each
backend where the model is compiled.
Built-in functions in the standard library include:
The functions
sqrt,log,expthat work on floating-point numbersFunctions that convert between different numerical types:
truncate,float,rat,floor,ceil,numerator,denominatorString functions:
substr,strlen,toString,printlnClock access:
currentms,ms_since_model_startFunctions that return process attributes (see Section Processes and process attributes)
The
randomfunction
Embedded SQLite database queries¶
The Erlang and Java backends can read from a relational database and convert the result into ABS lists. Currently only SQLite databases are supported.
A SQLite database is queried by writing a function with a builtin
body with three or more arguments: a literal sqlite3, the name of
the database file, the query as a SQL string, and zero or more
arguments to the query. The return value is a list of ABS values.
The return type of such a function can be:
List<Int>, when the query returns rows with a single SQLiteINTEGERvalue;List<Float>, when the query returns rows with a single SQLiteINTEGERorREALvalue;List<Rat>, when the query returns rows with a single SQLiteINTEGERorREALvalue;List<Bool>, when the query returns rows with a single SQLiteINTEGERvalue, where0corresponds to ABSFalse;List<String>, when the query returns rows with a single SQLiteTEXTvalue;A list of an algebraic datatype, with a single constructor taking only the above datatypes, such that the constructor can be invoked for each row returned by the query.
Query parameters are written as ? in the SQL query string. Each
of these parameters must be supplied with a value. ABS query
parameters are converted into SQL values (see
https://www.sqlite.org/datatype3.html) as follows:
Intvalues are converted intoINTEGER;Floatvalues are converted intoREAL;Ratvalues are converted into floating-point numbers before passing them to the query function;Boolvalues are converted into0and1(note that SQLite treats all non-zero values as true; consider directly using a query parameter of typeIntinstead);Stringvalues are converted intoSTRING.
Example: creating a database from the command line:
$ sqlite3 /tmp/test.sqlite3
CREATE TABLE IF NOT EXISTS test_table (
int_value INTEGER,
float_value REAL,
string_value TEXT,
bool_value BOOLEAN
);
INSERT INTO test_table(int_value, float_value, string_value, bool_value)
VALUES (15, 13.53, "hello", 0);
INSERT INTO test_table(int_value, float_value, string_value, bool_value)
VALUES (30, 42.5, "world", 1);
.quit
With the above database, the following ABS model can be run:
Example: reading from the database:
module Test;
def List<String> fstring() = builtin(sqlite3, ①
"/tmp/test.sqlite3",
"SELECT string_value FROM test_table");
def List<Rat> frat() = builtin(sqlite3, ②
"/tmp/test.sqlite3",
"SELECT float_value FROM test_table");
data RowResult = RowResult(Int, Bool, Float, Rat, String);
def List<RowResult> ftuple() = builtin(sqlite3, ③
"/tmp/test.sqlite3",
`SELECT int_value, bool_value, float_value, float_value, string_value
FROM test_table`);
def List<RowResult> ftuple_with_params(String str, Rat rat) = builtin(sqlite3,
"/tmp/test.sqlite3",
`SELECT int_value, bool_value, float_value, float_value, string_value
FROM test_table
WHERE string_value = ? ④
AND float_value = ?`,
str, ⑤
rat);
{
foreach (v, i in frat()) {
println(`$i$'th rational value is $v$`);
}
foreach (v, i in fstring()) {
println(`$i$'th string value is $v$`);
}
foreach (v, i in ftuple()) {
println(`$i$'th tuple is $v$`);
}
foreach (v, i in ftuple_with_params("world", 85/2)) {
println(`$i$'th tuple is $v$`);
}
}
For the Erlang backend, when the database file (the second argument to
the builtin expression) does not contain a path, the model will
look for it in the priv directory of the compiled model. That
directory is the value of the erlang function
code:priv_dir(absmodel), typically
gen/erl/absmodel/_build/default/lib/absmodel/priv/. For the Java
backend, the path to the database file is resolved relative to the
current path of the process running the model.