Terminal Entities

Terminal entities in EL correspond to the EL_TERMINAL meta-type in BMM, and its descendants. These come in three categories:

  • instance references: references to instances generated by direct references to literals, constants, variables, or else function calls;

  • predicates: logical conditions on instance references;

  • agents: delayed routine call objects.

The following sub-sections describe these types.

Literals

Literal values are mostly instances of the types declared in the imported models. Assumed literals correspond to the openEHR Foundation and Based types, and are expressed in the openEHR ODIN syntax, with the exception of List<T>, Set<T> and Map<K,V>, which are distinguished in EL with specific types of brackets. The corresponding classes are described in the openEHR Foundation Types specification.

Type Literal values Description

Boolean

True, False

Integer

10, -4, 5e09

Signed integer values from −231 to 231-1, including E-notation

Real

10.0, 0.345, 22.5%, 6.023e23

Signed real values from 3.4028235 × 1038, including percentages and E-notation

Double

10.0, 0.345, 22.5%, 6.023e23

Double precision real values, including percentages and E-notation

Date

2004-08-12

ISO 8601-format date

Date_time

2004-08-12T12:00:59+0100

ISO 8601-format date/time

Time

12:00:59

ISO 8601-format time

Duration

P2Y8M

ISO 8601-format duration

String

"this is a string"

Uri

<https://en.wikipedia.org>

Uri in IETF RFC 3986 format

Terminology_code

#identifier
#snomed_ct::389086002
#snomed_ct::389086002|Hypoxia|

Local terminology code
Terminology code in openEHR format

Array<T>

[val, val, …​]

List<T>

(val, val, …​)

Set<T>

{val, val, …​}

Map<K, V>

{
    prim_val : val,
    prim_val : val,
    ...
    prim_val : val
}

A table of values of any type V,
keyed by primitive values, typically String,
Integer or Date/time types.
<val> are literals of any other type.

Object

{
    identifier : val,
    identifier : val,
    ...
    identifier : val
}

An object of any type T,
whose property names are identifier.

Interval<T>

|N..M|

the two-sided interval N ≥ x ≤ M

|>N..M|

the two-sided interval N > x ≤ M

|N..<M|

the two-sided interval N ≥ x < M

|<N|

the one-sided interval x < N

|>N|

the one-sided interval x > N

|<=N|

the one-sided interval x ≤ N

|>=N|

the one-sided interval x ≥ N

|N +/-M|

the two-sided interval of N ±M

|N±M|

the two-sided interval of N ±M

One exception to the above is tuples, which are direct instances of the BMM meta-type BMM_TUPLE. They take the literal form [a, b, c], where a, b, and c are generally of different types.

TBD: consider to not bother with Array and reserve [] for tuples.

Variables

Symbolic variables are valid within the scope of the routine in which they are declared, and are classified as read-only or writable. Read-only variables include routine parameters and the automatically declared variable Self, and are reprsented by the meta-type EL_READONLY_VARIABLE.

Writable variables include locally declared variables and the automatically declared variable Result and are represented by the BMM meta-type EL_WRITABLE_VARIABLE.

Self

As with many programming languages, a pre-defined read-only reference to the current object is available via the plain name Self. Unlike some languages, 'Self' is not needed as a qualifier for properties or functions, and is generally only used as an argument in function calls.

Result

In EL, the variable Result is automatically declared on entry to any function to be of the return type of the function. It is a writable variable and the function will return whatever value has been assigned to Result during execution.

Type References

A type may be directly referenced using the syntax T. This has the effect of creating an anonymous variable whose value is a read-only instance of the type. This may be equivalently understood as the 'static view'. A type reference can be the scoper of any feature call that is to read-only and is:

  • a static feature, i.e. a constant or class singleton;

  • any function that depends recursively only on constants and static features in addition to any arguments.

This provides a mechanism, common to many programming languages, for access to constants and helper functions without creating instances.

Feature References

Qualified Referencing

Any feature reference may appear as itself (in the relevant syntactic form described below) or in a form qualified by scoping entities, using standard 'dot' referencing. The qualifier provides the reference context, and is represented by the EL_FEATURE_REF property scoper. Multiple qualifiers may be used in a single reference, as long as class feature visibility is satisfied, allowing the following:

    person1.name
    employees[1].name.first_name
    blood_pressure.history.events[3].data.data.systolic

    agent obstetric_risks.basic_risk

Constants

Constants are syntactically represented using labels of which the first letter is capitalised, and may be of any type, including complex types. The following are EL expressions containing constants.

    Mph_to_kmh_factor = 1.6
    Safe_glucose_limits.has (3.5)

Property References

Property references are valid within the scope of the class in which they are declared, and may be used in any routine definition or assertion in the class. They are represented by plain names such as diabetic_status.

Function Calls

In EL expressions, computational functions may be called in the same way as for typical programming languages. An EL property reference corresponds to the BMM meta-type EL_FUNCTION_CALL, which contains an instance of the BMM meta-type EL_FUNCTION_AGENT, which in turn has as its closed_args a tuple containing a set of items each of which is in turn an expression of any kind.

Consequently, EL function calls (similarly to most programming languages) may be of any level of complexity. The simplest type of function call is to a function whose signature is <[],T>, i.e. one taking no arguments are returning a value of type T. In EL, this may be called with or without parentheses, e.g. age or age().

The following example assumes a function tnm_major_number (tnm_val: String): Integer that extracts various elements of Tumour/Node/Metastasis ('TNM') cancer staging values, such as 'Tis', 'G3' and so on, and shows two forms of call to this function.

    tnm_major_number (tnm_t)
    tnm_major_number ("Tis")

More complex function calls may include arguments of other function calls, agents, tuples, operator expressions and normal instance references.

To be evaluated, function calls must be mappable to class methods in external libraries that are available at expression evaluation time.

Built-in Functions

Some commonly used functions such as current_date() or similar are often thought of as 'built-in' to a language environment. In the openEHR EL context, there are no built-in functions as such; useful utility functions must be supplied by classes or interfaces included as part of an imported model. In the openEHR environment, many utility calls are available in the openEHR Base Types. They will resolve correctly as long as this model is imported, which it normally will be as part of a larger model, such as the openEHR RM.

As a consequence, the total set of available utility functions for use in an EL expression is just what is available from the sum of all imported models. Assuming the openEHR Foundation and Base Types, the following kinds of functions are available for use in EL expressions:

    {Env}.current_date                                -- obtain today's date as a Iso8601_date

    blood_glucose_list: List<Real>
    {Statistical_evaluator}.max (blood_glucose_list)  -- compute a maximum of Numerics

    {Locale}.language                                 -- the primary language in the locale as a Coded_term

Container Item Access

Access to members of instances of a container type may be achieved by normal functional means (typically functions like Array<T>.get() or List<T>.item()), and also via the [] operator, which is an alias for such functions defined on the relevant types, as follows.

Operator Function Meaning

[i]

Array<T>.item(i: Integer): T

i-th element of an array; 1-based

[i]

List<T>.item(i: Integer): T

i-th element of a list; 1-based

[k]

Map<K,V>.item(k: K): V

element at key k of a Map

TBD: to achieve this generically, the above map of operators to member functions of appropriate types needs to be supplied in the model supplying the types themselves.

Container element access may be used on any expression whose effective type is a container, including function calls.

Matching Objects

Matching of objects is possible via use of predicates using the [] syntax used after any variable or feature reference. This is achieved by supplying an agent argument whose signature is <[T], Boolean>, or in functional form, (v:T): Boolean. For non-container objects, the type T is the statically declared type of the object. If the object is of a container type (list, array etc) then the type T is the type of the container items.

The [] syntax is shorthand for the following assumed functions:

Type Function

Any

match (matcher: <T> Function <[T], Boolean>): T

Container<T>

match (matcher: Function <[T], Boolean>): List<T>

TBD: For Any, need type anchoring…​ or else generic functions.

Here, 'matching' is understood to mean obtain all matching items.

This enables a reference of the following form to be constructed (final line).

    class Book {
        title: String;
        pub_date: Date;
        country: Terminology_code;
    }

    book_list, old_spanish_books: List<Book>

    old_spanish_books := book_list [(b:Book) {b.title.contains("Quixote")}]

The part in {} is any Boolean-valued expression, and may therefore be an operator expression, e.g.:

    old_spanish_books := book_list [(b:Book) {b.title.contains("Quixote") OR b.pub_date < P1650Y AND b.country = #iso639::es}]

Since the function signature is invariant with respect to the container item type (here, Book), a shorter form can be used in which the b is assumed:

    old_spanish_books := book_list [title.contains("Quixote") OR pub_date < P1650Y AND country = #iso639::es]

In the above, the variable old_spanish_books is of type List<Book>, and in general may contain more than one item (as well as be empty). To obtain the first book in the list, the standard array reference syntax may be used, i.e. old_spanish_books[1]. By extension, the following is also legal:

    old_spanish_book: Book

    old_spanish_book := book_list [title.contains("Quixote") OR pub_date < P1650Y AND country = #iso639::es][1] -- safe if it is known that there is at least one

Operator expressions based on the types of the items in the container may be used. The following predicate uses the short form of the expression b.pub_date >= PY2003.

    book_list [pub_date >= PY2003]

Qualified referencing can be combined with selector agents to obtain an effect similar to the use of Xpath on XML data, as follows.

    book_list [title.contains("Quixote")][1].pub_date.year

For matching to work, there must be an appropriate function available on all container types. In the case of the openEHR Foundation types, this is match (<[T], Boolean>): List<T> defined on Container<T>; any equivalent function in a different model will do. The return type is nullable.

TBD: to achieve this generically, the map of operators to member functions of appropriate types needs to be supplied in the model supplying the types themselves.

Other short forms are available, making a predicate syntax reminiscent of Xpath possible, as follows.

Lambda expression Short form

object[(v:T) {expr using v}]

object[expr with implied v]

container[(v:T) {expr using v}]

object[expr with implied v]

(more)

…​

Predicates

EL predicates are special meta-operators that are used to express tests on runtime object structures.

Attached() Predicate

The attached() predicate is the EL equivalent of the expressions such as someVar == null (C, C++, C#, Java), some_var is None (Python) and similar. In EL, a reference is understood as being attached (or not) to a value. Attached status is therefore tested using attached (ref), and may be applied to any target of a BMM EL_INSTANCE_REF, which includes references to variables, properties, constants, functions and tuples.

Attached() returns a Boolean value, and thus may be negated, to form expressions such as:

    not attached (test_result) or else test_result.data.events[1].data.value > 6.5

Agents

Delayed routine calls for both functions and procedures may occur as terminals in an EL expression. The evaluation type (eval_type) of an agent is its signature. Syntactically, these take various forms. An agent can be created using a function or procedure visible in the current scope, using the keyword agent. The arguments list may range from empty to full. For a completely empty list, the routine name on its own may be used.

    |
    | define a naive obstetric risk function
    |
    obstetric_risk (age: Duration[1]; previous_pregnancies: Integer[1]): Coded_term[1]

    |
    | generate an agent with signature <[Duration, Integer], Coded_term>
    |
    agent obstetric_risk

For a partial argument list, ? symbols are used for the non-filled arguments. This generates an agent whose signature corresponds to the remaining open arguments. In the following example, an agent of the signature <[Integer], Coded_term> is generated, which, since the age of 38 years is supplied, may be thought of as a new function called obstetric_risk_38_years().

    agent obstetric_risk ('P38Y', ?)

Theoretically, an agent could be created with all arguments supplied, without the intention of immediate execution, e.g. agent obstetric_risk ('P38Y', 2), which would generate an agent of signature <[],Coded_term>. This could be later executed by simply using the receiver variable or parameter reference in the normal way, in a later expression.

Agents for procedure calls can be created in the same way as described above. In each case, the evaluation type is a signature of the form <[args]>, i.e. having no return type.