Expressions
Rune provides a small, controlled set of features for expressing simple logic – basic operations, comparisons and combinations of these into larger expressions. It’s intentionally limited so domain experts who are not software engineers can read it easily and avoid unintended behavior. Simple expressions can be built up using operators to form more complex expressions.
Logical expressions appear in:
Expressions are evaluated against a Rune object and return either:
- a single value (built in, complex, or enumeration), or
- a list of values of the same type
The type of an expression is simply the type of the value it produces – for example, a booleanBoolean Data type with only two values (yes/no, on/off etc). expression returns a boolean, and an expression that produces a Party is of type Party.
There are two main types of expressions, and these can be amended with standard operators and list operators:
1. Constant expressions
There are three types of constant expression.
1.1. Basic type constant
Expressions can be simple literal values. These are useful for comparisons to more complex expressions:
- number 2.0
- booleanBoolean Data type with only two values (yes/no, on/off etc).
True - string “USD”
1.2. Enumeration constant
An expression can refer to an enumeration value.
Syntax
<EnumName> -> <EnumValue>
Example
DayOfWeekEnum -> SAT
1.3. List constant
Or they can be declared as lists.
Syntax
[ <item1>, <item2>, <...>]
Example
[1,2]
["A",B"]
[DayOfWeekEnum->SAT, DayOfWeekEnum->SUN]
1.4 Constructing data types and records
Data types and records can be constructed using a syntax similar to JSONJSON Text-based, language-independent format with key-value pairs (eg Name: Dave)..
Syntax
<type name> {
<field name 1>: <field value 1>,
<field name 2>: <field value 2>,
etc
}
Example: A person might look something like this:
type Person:
honorific string (0..1) <"An honorific title, such as Mr., Ms., Dr. etc.">
firstName string (0..1) <"The natural person's first name.">
middleName string (0..*)
initial string (0..*)
surname string (0..1) <"The natural person's surname.">
suffix string (0..1) <"Name suffix, such as Jr., III, etc.">
dateOfBirth date (0..1) <"The person's date of birth.">
Example: Simple literals can be used to construct a person called “Dwight Schrute”.
Person {
firstName: "Dwight",
initial: ["D", "S"],
surname: "Schrute",
honorific: empty,
middleName: empty,
suffix: empty,
dateOfBirth: empty
}
Example: Or the values could be any arbitrary Rune expression.
Person {
firstName: "Dwight",
initial: ComputeInitials("Dwight Schrute"),
surname: "Schr" + "ute",
honorific: GetDefaultHonorificTitle(),
middleName: empty,
suffix: variable -> suffixOfDwight,
dateOfBirth: date {
year: 1998,
month: 11,
day: 4
}
}
1.5 Triple-dot syntax
Many Rune types – like those in the CDMCDM A standardised, machine-readable and machine-executable blueprint for how financial products are traded and managed across the transaction lifecycle. It is represented as a domain model and distributed in open source. – have lots of optional fields. Manually setting each unused field to empty can be tedious. The triple dot syntax ... solves this by automatically assigning empty to any attribute you don’t specify.
Person {
firstName: "Dwight",
initial: ["D", "S"],
surname: "Schrute",
...
}
1.6 Constructing record types
The same syntax can be used to construct a record such as date or zonedDateTime.
date {
year: 1998,
month: 11,
day: 4
}
2. Path expressions
A path expression retrieves the value of an attribute inside an object. Paths can be chained to access nested attributes.
Syntax The simplest path is just an attribute name.
type ContractFormationPrimitive:
before ExecutionState (0..1)
after PostContractFormationState (1..1)
condition: <"The quantity should be unchanged.">
if before exists ....
Use -> to access nested attributes:
<attribute1> -> <attribute2> -> <...>
Example: The penalty points of a vehicle owner’s driving license is being extracted:
owner -> drivingLicence -> penaltyPoints
The path operator can also be used to extract the field of a built-in record type such as date.
Example: We define a function that checks whether a date falls before the year 2000.
func FallsBefore2000:
inputs:
d date (1..1)
output:
result boolean (1..1)
set result:
d -> year < 2000
2.1 Null
If a path refers to a missing attribute, the result is null. In this case, chaining will continue to return null. For multi-valued expressions, null behaves as an empty 'list'.
Example: When drivingLicense is null, the final penaltyPoints attribute also evaluates to null.
owner -> drivingLicence -> penaltyPoints
2.2 Deep pathsdeep path A way to access a shared attribute across all options of a choice type, without needing to know which option is present. for choice types
Use deep pathdeep path A way to access a shared attribute across all options of a choice type, without needing to know which option is present. operator ->> to access common attributes across all options of a choice type.
With a choice type called Vehicle, each of its options exposing an attribute vehicleId, this identifier can be accessed directly using vehicle ->> vehicleId.
3. Operators
Rune provides a set of operators that let you combine expressions into more complex logic. These operators mirror the basics found in most programming languages:
- Conditional statements:
if, then, else - Comparison operators:
=, <>, <, <=, >=, > - BooleanBoolean Data type with only two values (yes/no, on/off etc). operators:
and, or - Arithmetic operators:
+, -, *, / - Default operator:
default - Switch operator:
switch - With-meta operator:
with-meta
3.1. Conditional statements
A conditional expression consists of:
if– followed by a booleanBoolean Data type with only two values (yes/no, on/off etc). expressionthen– followed by any expressionelse(optional) – followed by any expression
Behavior
- If the
ifcondition is True, thethenvalue is returned. - If it’s False, the
elsevalue is returned (ornullif noelseis provided).
Type rules
- The type and cardinalityCardinality The number of elements in a set or other grouping, as a property of that grouping. of the whole conditional expression comes from the
thenclause. - The
elseclause must match the type of thethenclause. - You can chain multiple conditions using
else ifstatements ending with a finalelse.
3.2. Comparison operators
Comparison operators always return a booleanBoolean Data type with only two values (yes/no, on/off etc)..
Equality
=– True if both sides are equal<>– True if both sides are not equal
Equality rules
- Built-in types compare by value.
- Complex types compare by recursively comparing all attributes.
Mathematical comparisons
<, <=, >=, > work on 'comparable types':
intnumberdatestring
Both sides must be the same type.
Existence checks
exists– True if the expression has a valueis absent– True if it does not
Modifiers:
only– this attribute exists, and no other attribute in the parent object does.single– the expression has exactly one value.multiple– the expression has more than one value
only exists replaces long conditions like:
- A exists
- B is absent
- C is absent
…and so on
It also stays correct even if new attributes are added later.
The only exists operator can apply to a composite set of attributes enclosed within brackets (..).
This condition is usually used on types that already enforce a one-of rule, meaning only one attribute can exist at a time. In those cases, only is technically redundant, but it makes the logic clearer and keeps the condition correct even if the one-of rule is removed in the future.
economicTerms -> payout -> interestRatePayout only exists
or (economicTerms -> payout -> interestRatePayout, economicTerms -> payout -> cashflow) only exists
Comparable types
These built-in types can be compared using mathematical comparison operators, as long as both sides have the same type.
intnumberdatestring
Comparisons and null
When a single value expression is null, comparisons behave like this.
null = value– Falsenull <> value– Truenull > value– Falsenull >= value– False
This applies even when the other value is also null. The behavior is symmetric – null < value works the same as value < null.
3.3. BooleanBoolean Data type with only two values (yes/no, on/off etc). operators
- Use
andandorto combine booleanBoolean Data type with only two values (yes/no, on/off etc). expressions. - Use parentheses
( )to group expressions.
Expressions inside parentheses are evaluated first.
3.4. Arithmetic operators
Rune supports basic arithmetic:
+ adds two numbers, or concatenates two strings
-, *, / subtracts, multiplies, divides two numeric values and always returns a number
3.5. Default operators
The default operator returns the first value unless it’s empty:
- If left has a value – return left
- If left is empty – return right
Both sides must have the same type and cardinalityCardinality The number of elements in a set or other grouping, as a property of that grouping..
3.6. Switch operators
The switch operator performs case analysis on a value.
Syntax
The right side lists case statements that specify what value to return when the input matches each case.
"valueB" switch
"valueA" then "resultA",
"valueB" then "resultB",
default "resultC"
Enumerations
You must cover all enumeration values or provide a default:
enumInput switch
A then "result A",
B then "result B",
C then "result C",
default "other"
The default case is optional. But if no case matches and no default is provided, the result is empty.
2
Using switch for choice types
You can use switch on choice types by matching the active option.
Example
Model for a powered vehicle.
choice PoweredVehicle:
PetrolCar
ElectricCar
Motorcycle
type PetrolCar:
fuelCapacity number (1..1)
type ElectricCar:
batteryCapacity number (1..1)
type Motorcycle:
fuelCapacity number (1..1)
Example
Inside each case, you can directly access attributes of that specific type.
Use item to refer to the actual object in the case.
func ComputeMileage:
inputs:
vehicle PoweredVehicle (1..1)
output:
mileage number (1..1)
set mileage:
vehicle switch
PetrolCar then 15 * fuelCapacity, // assume 15 kilometres per litre of fuel
ElectricCar then 5 * batteryCapacity, // assume 5 kilometres per kWh of battery
default 80 // for any other powered vehicle, assume a mileage of 80 kilometres
Nested choice types
Using switch also works when a case appears indirectly through another choice type.
Extended model
From the previous example we could extend our mileage computation to support all possible types of Vehicle.
choice Vehicle:
PoweredVehicle // as defined above
Bicycle
type Bicycle:
weight number (1..1)
Extended switch
Even though the Vehicle choice type does not list PetrolCar directly, it's included through PoweredVehicle, so it can be used as a case.
As with enumerations, all cases must be covered or a default must be provided. For example, leaving out the Bicycle case in this example will result in the switch operation being highlighted in red.
3
func ComputeMileage:
inputs:
vehicle Vehicle (1..1)
output:
mileage number (1..1)
set mileage:
vehicle switch
PetrolCar then 15 * fuelCapacity, // assume 15 kilometres per litre of fuel
ElectricCar then 5 * batteryCapacity, // assume 5 kilometres per kWh of battery
PoweredVehicle then 80, // for any other powered vehicle, assume a mileage of 80 kilometres
Bicycle then 30 // assume a mileage of 30 kilometres for a bicycle
Switch for complex types
You can also use switch on complex types.
By switching over a person, we can perform case analysis to compute a monthly wage.
type Person:
name string (1..1)
type Employee extends Person:
salary number (1..1)
type Contractor extends Person:
hourlyRate number (1..1)
Within each case, you can access attributes specific to that case directly. Use the keyword item to refer to the actual specific person inside each case.
Because complex types can be extended, a default case is required.
func ComputeMonthlyWage:
inputs:
person Person (1..1)
output:
monthlyWage number (1..1)
set monthlyWage:
person switch
Employee then salary,
Contractor then hourlyRate * 8 * 5 * 20,
default 0
3.7. With-meta operator
The with-meta operator lets you attach metadata to an expression using a constructor style block.
key– metadata is applied to the alias value,someType.scheme– metadata is applied to the output attribute.- Works with both
keyandreferencemetadata.
Note
- The expression to which you apply
with-metamust have single cardinalityCardinality The number of elements in a set or other grouping, as a property of that grouping.. - Metadata values you assign (e.g.
"someKey","someScheme") must also have single cardinalityCardinality The number of elements in a set or other grouping, as a property of that grouping..
type SomeType:
[metadata key]
someField string (1..1)
func MyFunc:
output:
result SomeType (1..1)
[metadata scheme]
alias someType: SomeType {
someField: "someValue"
}
set result: someType with-meta {
key: "someKey",
scheme: "someScheme"
}
3.8. Operator precedence
Rune evaluates expressions in a fixed order, from highest to lowest priority:
- RosettaPathExpressions: e.g.
Lineage -> executionReference - Brackets: e.g.
(1+2) - if-then-else: e.g.
if (1=2) then 3 - Constructor expressions: e.g
MyType {attr1: "value 1"} - Unary operators:
>, ->>, exists, is absent, only-element, flatten, distinct, reverse, first, last, sum, one-of, choice, to-string, to-number, to-int, to-time, to-enum, to-date, to-date-time, to-zoned-date-time, switch, sort, min, max, reduce, filter, map, extract, with-meta - Binary operators:
contains, disjoint, default, join - Multiplicative operators:
*, / - Additive operators:
+, - - Comparison operators:
>=, <=, >, < - Equality operators:
=, <> - and – e.g.
5>6 and True - or – e.g.
5>6 or True
4. List operators
A list is an ordered collection of items of the same type (basic, complex, or enumeration).
Key behaviors:
- A path expression pointing to a multiple cardinalityCardinality The number of elements in a set or other grouping, as a property of that grouping. attribute returns a list.
- Any expression expected to return multiple values always evaluates to a list, even if it contains zero or just one element.
- If such an expression returns null, Rune treats it as an empty list.
- Chained paths with multiple multi-cardinalityCardinality The number of elements in a set or other grouping, as a property of that grouping. attributes produce a flattened list.
Example
This returns a single list containing all vehicle entitlements from all of the owner’s licenses.
owner -> drivingLicence -> vehicleEntitlement
4.1 List operator keywords
Rune supports common list operations:
thenfilterextractreduce
then
The then keyword takes the result of the expression on the left and passes it as input to the expression on the right.
You can pass the entire left-hand result (for example, a list or empty) to a function that expects it.
Here, item represents the entire list of driving licenses.
set updatedVehicleOwnership -> drivingLicence: <"Overwrite existing driving licences with renewed driving licences.">
vehicleOwnership -> drivingLicence
//then without extract means that item is a list of driving licenses
then RenewAllDrivingLicences(item, newDateOfRenewal)
Iterating with extract
Use then extract to process each element individually.
add renewedDrivingLicences:
drivingLicences
//then with extract will pass driving licenses one by one to item
then extract RenewDrivingLicence(item, newDateOfRenewal)
Incorrect use of then
If the right-hand side does not use the input item, the left hand result is discarded. Rune raises a syntax error indicating that the input item is not used in the then expression.
add ownersName: <"Filter lists to only include drivers with first and last names, then use 'map' to convert driving licences into list of names.">
drivingLicences
extract person
then filter firstName exists and surname exists
then "someOutput"
To conditionally return firstName + " " + surname based on if surname exists, ensure that the right-hand side actually consumes the bound item by using extract.
add ownersName: <"Filter lists to only include drivers with first and last names, then use 'map' to convert driving licences into list of names.">
drivingLicences
extract person
then filter firstName exists and surname exists
then extract firstName + " " + surname
Filter
The filter keyword keeps only the items in a list that satisfy a booleanBoolean Data type with only two values (yes/no, on/off etc). condition.
Syntax
- The booleanBoolean Data type with only two values (yes/no, on/off etc). expression is evaluated for each item.
- If it’s True, the item is included.
- If it’s False, the item is excluded.
- The output list has the same item type as the input list.
- The default item name is item, but you can provide your own.
Example
Filter by engine type:
vehicles
filter [ item -> specification -> engine -> engineType = engineType ]
Example
Filter using a custom item name:
vehicles
filter vehicle [ vehicle -> specification -> zeroTo60 < zeroTo60 ]
Example
Chaining filters with conditions. Square brackets are optional unless needed to avoid ambiguity in nested expressions.
vehicles
filter (if carbonMonoxideCOLimit exists then specification -> engine -> emissionMetrics -> carbonMonoxideCO <= carbonMonoxideCOLimit else True)
then filter (if nitrogenOxydeNOXLimit exists then specification -> engine -> emissionMetrics -> nitrogenOxydeNOX <= nitrogenOxydeNOXLimit else True)
then filter (if particulateMatterPMLimit exists then specification -> engine -> emissionMetrics -> particulateMatterPM <= particulateMatter
Nested list operations
You can nest list operations inside each other.
Example
Find all owners whose licenses do not exceed a penalty point limit.
owners
filter owner [ owner -> drivingLicence
filter licence [ licence -> penaltyPoints > maximumPenaltyPoints ] count = 0
]
Extract
The keyword extract transforms each item in a list using an expression.
- The expression runs once per list item.
- The output is a new list containing the transformed items.
- Square brackets are optional unless needed to avoid ambiguity.
Syntax
<list> extract (optional <itemName>) [ <expression> ]
Example
Convert driving licences into a list of names.
drivingLicences
extract person
filter firstName exists and surname exists
then extract firstName + " " + surname
Reduce
The reduce keyword is an operation that takes a list and returns a single value.
Built-in reduction operators
sum– sum of all numbersmin, max– smallest or largest elementjoin– concatenates strings (optional delimiter)reduce– custom reduction using a merge expression
Syntax
<list> <reduceOperator> (optional <itemName>) (optional [ <operationExpression> ])
Example
vehicles
max [ item -> specification -> engine -> power ]
For join, the operator can specify a delimiter expression in-between each string element. This example concatenates a list of strings, separating each element with the given delimiter:
strings join ";"
3
vehicles
reduce v1, v2 [
if v1 -> specification -> engine -> power > v2 -> specification -> engine -> power then v1 else v2
]
Reduction works by repeatedly combining two list elements into one until only a single result remains. All built in reduction operators are just shortcuts for this generic reduce process.
When using reduce, you must provide an expression that defines how to merge two items. The earlier example that finds the vehicle with the highest engine power can be rewritten.
4.2. List comparison
Rune supports comparison operators on lists. All list comparisons return a booleanBoolean Data type with only two values (yes/no, on/off etc)..
4.3 Additional list comparison keywords
all / any– compare a list to a single valuecontains– True if the right side is a subset of the leftdisjoint– True if the lists share no elements
If a single value expression is used with contains or disjoint, Rune treats it as a list with one element (or empty if null).
4.4 Rules
- If one side is a list and the other is a single value, you must use
allorany. - If both sides are lists, they must have the same length for pairwise comparisons.
4.5 Semantics
=
- List vs list – True if same length and all items match
- List vs single (all) – all items equal the value
- List vs single (any) – at least one item equals the value
<>
- List vs list – True if lengths differ or any item differs
- List vs single (any) – any item differs
- List vs single (all) – all items differ
<, <=, >=, >
- List vs list – lists must be same length; comparison must hold pairwise
- List vs single (all) – comparison must hold for all items
- List vs single (any) – comparison must hold for at least one item
4.6 Other list operators
These operators all require that the expression has multiple cardinalityCardinality The number of elements in a set or other grouping, as a property of that grouping.:
- only-element – returns the single element if the list has exactly one item
- count – number of items
- first, last – first or last element
- flatten – flattens a list of lists
- sort – sorts comparable elements
- distinct – removes duplicates
Examples
The only-element keyword imposes a constraint that the evaluation of the path up to this point returns exactly one value.
If the list has 0, 2, or more items – result is null.
observationEvent -> primitives only-element -> observation
You can use the distinct operator to remove duplicate elements from a list.
Combine it with other keywords like count to determine if all elements of a list are equal.
owner -> drivingLicense -> countryofIssuance distinct count = 1
5. Conversion operators
Rune provides five conversion operators:
to-enumto-stringto-numberto-intto-time
Examples
Given:
enum Foo: VALUE1 VALUE2 displayName "Value 2"
These values will result:
- Foo -> VALUE1 to-string – "VALUE1"
- Foo -> VALUE2 to-string – "Value 2" (uses display name)
- "VALUE1" to-enum Foo – Foo -> VALUE1
- "Value 2" to-enum Foo – Foo -> VALUE2
- "-3.14" to-number – -3.14
- "17:05:33" to-time – time value 17:05:33
If conversion fails – the result is empty.
"VALUE2" to-enum Foo– empty (display name mismatch)"3.14" to-int– empty
5.1 Keyword clashes
If a model name clashes with a Rune keyword, prefix it with the ^ operator. The ^ is not included in generated code or JSONJSON Text-based, language-independent format with key-value pairs (eg Name: Dave)..
Example
In this example, ^E avoids conflict with the E notation from mathematics, which is supported by Rune.
Generated code and JSONJSON Text-based, language-independent format with key-value pairs (eg Name: Dave). will simply use E.
enum VehicleTaxBandEnum:
A
B
C
D
^E
F
G