Expression Language

This topic presents expression language syntax.

This page discusses:

Learn by Example

Let's start with a boolean expression:

person.firstName == "Bob" AND (NOT person.age < 18)

The use of logicial operators (AND, NOT) is similar to the use of logical operators in SQL. For more information, see Operators.

Note: person here is a variable.

Tip: To add comments in your expressions, use /* */, not //.

Call a Function Specific to a Namespace

Use the syntax <Namespace>.<functionName>:

/* Here `Text` is the namespace and `hasTerm` the specific function that will look for occurrences of "java". */
Text.hasTerm(person.resume, "java")

For more information about full-text search functions, see Functions.

Create a Tuple (a composite object)

{name: person.firstName, bmi: person.weight / Math.pow(person.height, 2)}

It looks like a JSON-object notation, and it behaves like 'structs' in C. For more information, see "Tuple" in Types.

Append an Element to a List

[...person.phones, "0123456789"]

The spread operator ... performs in-place expansion. It also works on Tuples.

Find an Element in a List

List.anyMatch(person.phones, phone => phone == "0123456789")

Note: You can also call a function directly from on an object, using the following function extension:

person.phones.anyMatch(phone => phone == "0123456789")

Notes:

  • See the use of a lambda expression as parameter of the anyMatch function.

  • The type of lambda parameter is infered here. For more information, see Lambda Functions.

Sanitize NULL Values

For example, replace NULL values by a default value called Foo:

person.firstName IS NULL ? "Foo :(" : person.firstName

In this example, we have a ternary operator, that uses the syntax <CONDITION> ? <EXPRESSION1> : <EXPRESSION2>If person.firstName does not have any value, it will return Foo. Otherwise, it will return the value of the person.firstName variable.

This is equivalent to person.firstName || "Doh :("

Note: The expression language tries to be as robust as possible at run time to avoid having with NullPointerExceptions For example:

  • The expression: 1 + NULL returns NULL

  • person.firstName returns NULL if person is NULL

Make a String Concatenation

person.firstName + " is " + person.age + " years-old"

Types

The expression language is strongly typed. Note: All types are immutable and nullable.

Here is the list of types in the expression language.

Name Description Literals or constructor
String A sequence of characters. "abc", "\u263A" (unicode)
Integer Signed 64 bits integer. 123
Float Signed 64 bits floats 1.23, 1.23E-9
Decimal Signed 8-byte decimal number. Literals are suffixed with the character 'd' 1.23d
Boolean Boolean true / false
Date A date without timezone (year, month, day). Date.of
DateTime A date-time without timezone (year, month, day, hour, minute, second). DateTime.of
Period A span of time expressed in years, months and days. Period.of
Duration An amount of time expressed in seconds. Duration.of
Geometry A vector geometry object (point, polygon, ect.), represented in WKB format (well-known binary). Geo.fromWkt
Binary Opaque byte array No constructor
List<E> An ordered collection. Elements can be accessed by their index. ["foo","bar"]
Set<E> Unordered collection with unicity guarantee Set.of
Map<K, V> Key / Value dictionary. You can use any type as either key or value. Known limitation: map values (i.e. V) cannot be NULL. ["foo" : p1.age, p2.name : 15]
Tuple<K1: V1, K2: V2, ...> An object that encapsulates values of multiple types. It is composed of an ordered list of entries. Each entry is defined with a name and a type. For example, the type Tuple<firstname: String, age: Integer> defines a Tuple that encapsulates a value of type String and a value of type Integer, along with their firstname and age accessors. {age: 15, name: "Francis"}
Comparable Any type that you can use to sort on (Integer, Float, String, Date, DateTime and Duration). You typically use this type in a Sort or a Rank operator. It is not serializable, and therefore, it must not be present in a service output object.
Any Any type. This type is not serializable, and therefore, it must not be present in a service output object.
Function<ParameterType1, ..., ReturnType> Lambda function foo => foo.age > 21
Item<pkg.class> Represent an Item coming from an Index Unit No constructor
TextMatch Represent the result of text matching functions. No constructor
NULL Type used for NULL value when needed NULL

For more information about the AS and IS operators, see Expression Operators.

Variables

Context Variables

Expressions are executed in a specific context, which in most cases, contains one or more variables. These variables are typed and immutable. The number, the names, and the types of the variables, depend on the place where you define the expression.

In the User Interface, the Expression Editor displays the list of variables (name and type) available for an expression.

person1 IN person2->friends

In this expression, person1 and person2 are context variables, and you can find if person1 is present in the list of friends of person2.

Local Variables

It is possible to define local variables inside an expression. This allows you to reuse a value in different places without duplication.

Example:

now:DateTime = DateTime.now();
{
    month:now.month,
    year: now.year
}

In this expression, now:DateTime is a local variable to avoid repeating DateTime.now() several times. Without this local variable, you would have to write:

{
    month:DateTime.now().month,
    year: DateTime.now().year
}

Global Variables

A global variable is a variable that is accessible to any graph processing operator in a graph. A global variable starts with the @ sign, followed by the name of the variable.

To declare a global variable in a graph, add the ToVariable operator. You cannot use a global variable before it is declared, that is, you cannot use it in any operator that precedes the ToVariable operator.

Example: In a graph with a ToVariable operator that declares a variable named average, you can use the variable as follows:

person.age > @average

Graph Parameters

A graph parameter is a value or a function that is provided by the caller of the graph. At top-level, input handlers fill graph parameters.

A graph parameter is accessible to any operator in the graph it is defined in. It starts with the $ sign, followed by the name of the parameter.

Example: In a graph that defines a parameter named limit that returns an Integer, the following expression is valid:

person.age > $limit

If the placeholder has no arguments, parentheses are optional. The previous example is strictly equivalent to:

person.age > $limit()

Parameters may have arguments. In that case, the caller of the graph provides a function instead of a value. Example: In a graph that defines a parameter named predicate that accepts an Integer as argument and returns a Boolean, the following expression is valid:

$predicate(person.age)

When this graph is called, the following expression is a valid value for the predicate argument: "age > 10".

Expression Operators

The following subsections use examples based on this Data Model:

package app

class Issue {
    related : List<Reference<Issue>>
    title : Text
    description : Text
    reporter : Reference<Person>
    owner : Reference<Person>
}

class Person {
    firstName : String
    lastName : String
    rolesByContext : Map<List<Reference<Role>>>
}

class Role {
    title : Text
    description : Text
}

class Comment {
    issue : Reference<Issue>
    author : Reference<Person>
    publicationDate : DateTime
}

Field Access

Operator Description Sample
. fields (Tuple) or items p.price
[] attributes (Item) and properties access map["key"], list[0]
-> The arrow operator allows you to specify traverse Reference from Index Unit Items.The -> allows you to specify a direct traverse. issue->owner.firstNameissue->owner->rolesByContext["editing"]issue->related.firstNote: You can apply -> on: List<Reference<>>, Map<Reference<>>, and Map<List<Reference<>>>, and not just on Reference<>.This operator returns an Item or a List<Item>, depending on the type of attribute it has been applied to.Warning: Even if the arrow operator is somehow comparable to pointer dereferencing in C language, it is actually different, and you should not expect to get the same behavior.
<- The reverse arrow operator <- always returns a List<Item>, as it is not possible to specify the cardinality of the relation. issue<-[app.Comment.issue].sort(comment=>comment.publicationDate)In this example, we can see that a Comment references an Issue, but nothing can tell the engine whether there are one or several Comments that reference a Ticket.

Boolean Operation

Operator Description Sample
NOT Boolean NOT NOT foo IS NULL
AND Boolean AND true AND false
OR Boolean OR true OR false

Conditional Expressions

Operator Description Sample
switch Similar to the If else operator. It is more readable when you have complex expressions. switch (p.age) { case 20, 21 => "winner" case 18, 19 => "looser" default => "n/a" }
|| Coalesce p.price || defaultPrice
?: Ternary operator foo == "bar" ? 6 : 4
if If else operator. Parentheses and curly bracket are mandatory."else" section is optional. if(p.age > 18) { "adult" } else { "child" }

Comparison

Operator Description Sample
<= >= > < Relational operators p.age < 4 page.age IS Integer
== Equal operator
!= Different operator
IS Use this operator to test the type or the nullity of an object. p IS Integerp IS Item<social.Person>p IS Map<String, String>p IS Tuple<firstName:String, lastName:String, age:Integer>p IS NULLThe IS operator does not apply implicit casting, which means, for example, that "2 IS Float" or "2 IS Any" return false.However, IS supports the inheritance of Items, which means that if the Employee class extends Person, employee IS Item<social.Person> returns true.

Number Manipulation

Operator Description Sample
+ - As unary operators -2
* / % Multiply, Divide, p.age*4 p.age / 4 p.age % 4
+ - As binary operators p.age + 4

Array Manipulation

Operator Description Sample
IN Contained in p.age IN [4,5,6]
... The spread operator expands a structure in place into another structure.Note: You can apply the spread operator on a tuple or on a list. The most simple use case is to append an element to a list.[...list, 4]But you can implement more complex cases, like:[...list1, 4, ...list2]

Tuple Manipulation

Operator Description Sample
... The spread operator expands a structure in place into another structure.Note: You can apply the spread operator on a tuple or on a list. tuple : Tuple<firstName:String, lastName : String> newTuple = {...tuple, age: 4}; newTuple IS Tuple<firstName:String, lastName:String, age:Integer> {...tuple1, age:4, ...tuple2}

Cast Operation

Operator Description Sample
AS Allows you to cast an element to compatible type. p AS DateTime p.age AS Float p AS Item<social.Person> p AS Any

Bitwise Operation

Operator Description Sample
<< >> Bitwise shift operators p.age << 4
& Bitwise AND p.age & 4
^ Bitwise XOR p.age ^ 4
| Bitwise OR p.age | 4

Functions

When calling a function, you can optionally use the name of the parameters explicitly in the call. This is to improve the function readability. For example, the following function calls are all equivalent:

Date.of(2016, 1, 31)
Date.of(year: 2016, month: 1, day: 31)
Date.of(day: 31, month: 1, year: 2016)
Date.of(2016, 1, day: 31)

You can call some functions directly on a variable. These 2 syntaxes are equivalent:

values : List<Integer> = [1,3,5,7];
values.filter(value => value % 3 == 0)
List.filter(values, value => value % 3)

To see which functions can be called directly, use the autocomplete on a variable. For more information about the lambda function used in the sample, see the following section.

See the list of all functions available here.

Lambda Functions

General syntax is:

(parameterName1:parameterType1, parameterName2:parameterType2, ...) => expression

When there is only one parameter, you can omit the parentheses. When the type can be infered, you can omit the parameter type too.

Examples:

foo => foo + 2
(foo:Integer, bar:Integer) => (foo * bar)
(foo:Integer, bar) => foo + bar

You can use a lambda function as a parameter of a function (the parameter type must be Function, for more information, see Types):

values : List<String> = ["foo", "bar"];
values.anyMatch(value => value == "bar")