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.
Navigate through Item References
For example, to get my grand father's first name:
person->father->father.firstName
For more information, see Arrow.
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:
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.firstName issue->owner->rolesByContext["editing"] issue->related.first Note:
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 Integer p IS
Item<social.Person> p IS
Map<String, String> p IS
Tuple<firstName:String, lastName:String,
age:Integer> p IS NULL The
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")
|