Expressions

ALFA support expressions within assert and library definitions. Expression capabilities compliment the modelling features to interact with data.

Writing rules as ‘code’ can seem challenging to some, but if you have used Excel cell functions, calculations, you are over half way there on ALFA expressions!

Expressions can be combined to build rules for ALFA object data validation. Expressions can include various Builtin Functions and object creation constructs listed below. Similar to the Type System, the expressions can be generated into target languages to be executed on the data natively.

Expression Constructs

ALFA expression syntax is minimalistic, data-oriented and functional. The builtin functions serve to operate on data defined by the Type System.

Expressions are enclosed within 3 types of statements:

  1. Local variable declaration
  2. Function call
  3. return statement

No loop constructs - for, while etc are supported. Expressions are used in a functional-style with built-in higher order functions.

Conditional statements - if, else are supported as expressions:

...
let a = if ( b ) 0 else 20
return if ( a == 0 ) "no result" else "has result"

For cases where ALFA Expressions cannot sufficiently express a requirement using the built-in syntax, or there is existing native code that achive the same functionality, an ALFA service can be declared and implemented natively, which can be used from assert or library.

Function Calls

To help writing assert and other expressions, ALFA supports 50+ Builtin Functions. These range from higher-order functions to data-type conversion functions. ALFA code-generators are fully aware of built-in functions and will generate highly-optimised native code corresponding to the function usage.

Functions/features not available can be implemented as function calls on a service, that can have custom native implementations. Based on demand and usecases, the list of functions will expand in future.

Refer to Builtin Functions for the full list of current functions.

Tips

Method Chaining

To avoid deep nesting of function calls ( e.g. filter( map(students, e => new Applicant(e) ), e => e.Age >= 18) ), they can be chained using the pipe (|) symbol to make the logic easier to read.

map(students, e => new Applicant(e) ) | filter( e => e.Age >= 18 )

Optional Field Access

ALFA has no concept of null and expects safe access to fields marked optional. When using dot-qualified access, if a value is optional, the presence of a value needs to checked (using isSome( <the optional value> )) and accessed with get( <the optional value> ).

To simplify accessing nested optional values, or traversing through optional values, ALFA supports ‘Optional Chaining’.

E.g. Consider having a field of type AltAddress : Address ? with type Address containing Line1 : string. The following will give a compile error, as AltAddress may be unset - None.

let line1 : string = AltAddress.Line1

Using optional chaining:

let line1 : string? = AltAddress?.Line1

Regardless of a level of nesting, using ?. will safely access fields and return an optional value of the last field accessed.

Local variables

ALFA expressions can reference fields within a user-defined type or locally declared variables. Variables are declared using the let keyword.

Variables cannot be reassigned, therefore immutable. Note that collection types such as map< K, V > can have values added using mutator method such as put.

let count = 1
let nums = [1, 2 ,3]

ALFA’s expression type inference system will infer the type of nums above to be a list<short>. If necessary, the type can be explicitly specified.

let nums : list<int> = [1, 2 ,3]

Lambda Expressions

Lambda expressions are supported to be used with with higher-order functions. The example below shows lambdas in use.

Single argument lambdas can be specified as a single identifier, multiple arguments need to be surrounded by parenthesis as shown in the example below.

passengers : set< Person >
...
let adults = filter( passengers, p => p.Age >= 18 )
let totalLuggageWeight : int = reduce( passengers, 0, ( total, p ) => total + p.luggageWeight )

If the body of the lambda is a single expression ( E.g. p.Age > 18 above), it can be simply specified as-is. If multiple statements are required to get the result of the lambda body, it needs to be wrapped in braces and a final return statement.

passengers : set< Person >
...
let adultGoldMember = filter( passengers, p => {
                                            let adult = p.Age >= 18
                                            let gold = p.Membership == "Gold"
                                            return adult && gold
                                          } )

For more usages, see reduce, filter and map.

Decision Table Expressions

A powerful and concise expression feature is decision table and bucketing.

This enables [Decision table](https://en.wikipedia.org/wiki/Decision_table) based rules to be expressed in a table, where each row represents input decision choices and the final column expressing the output.

Furthermore, ‘bucketing’ can be expressed using decision table expressions.

See decision table and bucketing for details.