FunctionsFunctions mk Mon, 04/20/2020 - 17:38
MAL comes with a standard functional abstraction scheme. Functions are represented by MAL instruction lists, enclosed by a function signature and end statement. The function signature lists the arguments and their types. The end statement marks the end of this sequence. Its argument is the function name.
An illustrative example is:
function user.helloWorld(msg:str):str; io.print(msg); msg:= "done"; return msg; end user.helloWorld;
The module name 'user' designates the collection to which this function belongs. A missing module name is considered a reference to the current module, i.e. the last module or atom context openend. All user defined functions are assembled in the module user by default.
The functional abstraction scheme comes with several variations: commands, patterns, and factories. They are discussed shortly.
Functions can be pre-pended with the keyword unsafe, which designates that execution of the function may change the state of the database or sends information to the client. Unsafe functions are critical for the optimizers, because their order of execution should be guaranteed. Functions that return a value of type :void are considered unsafe by default.
Functions prepended with the keyword inline are a target for the optimizers to be inlined. This is particularly useful when a vectorized version can be used in cases where only scalar function is known.
C-functionsC-functions mk Mon, 04/20/2020 - 17:45
The MAL function body can also be implemented with a C-function. They are introduced to the MAL type checker by providing their signature and an
address qualifier for linkage.
We distinguish both command and pattern C-function blocks. They differ in the information accessible at run time. The command variant calls the underlying C-function, passing pointers to the arguments on the MAL runtime stack. The pattern command is passed pointers to the MAL definition block, the runtime stack, and the instruction itself. It can be used to analyse the types of the arguments directly.
For example, the definitions below link the kernel routine BKCinsert_bun with the function bat.insert(). It does not fully specify the result type. The io.print() pattern applies to any BAT argument list, provided they match on the head column type. Such a polymorphic type list may only be used in the context of a pattern.
command bat.insert(b:bat[:any_1,:any_2], ht:any_1, tt:any_2) :bat[:any_1,:any_2] address BKCinsert_bun; pattern io.print(b1:bat[:any_1,:any]...):int address IOtable;
PolymorphismPolymorphism mk Mon, 04/20/2020 - 17:43
Polymorphic functions are characterised by type variables denoted by
and an optional index. Each time a polymorphic MAL function is called, the symbol table is first inspected for the matching strongly typed version. If it does not exists, a copy of the MAL program is generated, whereafter the type variables are replaced with their concrete types. The new MAL program is immediately type checked and, if no errors occured, added to the symbol table.
The generic type variable :any designates an unknown type, which may be filled at type resolution time. Unlike indexed polymorphic type arguments, :any type arguments match possibly with different concrete types.
An example of a parameterised function is shown below:
function user.helloWorld(msg:any_1):any_1; io.print(msg); return user.helloWorld; end helloWorld;
The type variables ensure that the return type equals the argument type. Type variables can be used at any place where a type name is permitted. Beware that polymorphic typed variables are propagated throughout the function body. This may invalidate type resolutions decisions taken earlier (See MAL Type System).
This version of helloWorld can also be used for other arguments types, i.e. bit,sht,lng,flt,dbl,.... For example, calling helloWorld(3.14:flt) echoes a float value.