Properties, conditions, assignments, etc. are all defined as expressions of the LSPS Expression Language. Expressions use of operators, literals, keywords, and other constructs of the Expression Language, that end up returning a value of a type. Also literals are of a type, which does not change (unless cast but we will get to that later).
You can test expressions in the Expression Evaluator REPL view. By default, the view is not displayed: To display it, go to Window > Show View > Other and in the displayed dialog search for the view.
Important: The REPL view does not support calls to Standard Library resources, such as functions, data types, etc.
Note: When evaluating your expression in the Expression Evaluator REPL view, the expressions are evaluated in a "dummy context" outside of any model: under normal circumstance, on runtime, expressions are evaluated in a context of their model instance: so they can access data in their parent contexts. Contexts are represented by Instances of name-space elements: These elements include Modules, Processes, Plans, and sub-Processes. To fully understand when a context is created and what data it holds, refer to namespaces and contexts.
Let's try it out:
Enter the following into the Expression Evaluator REPL and try to predict the return value:
1 + 1
1 + 1.12
1/1.1
1%1.1
3**3
"Hello, " + "world!"
"Hello, number " + 1
1 + "hello"
This results in an invalid expression since the + operator is interpreted as addition, not concatenation."Anne" < "James"
Strings are compared lexicographically: A appears before J in alphabet.
"James" like "J*"
"James" like "J???s"
"James" like "Jo*"
1 == 1,00
"Bob"<=>"Anne"
"Bob"<=>"John"
"Bob"<=>"Bob"
true and false
true and true
false and false
true or false
false or true
true or true
true xor false
false xor true
true xor true
true xor true
!true
!false
"This will not return!";"Hello, " + #10 + "world!"
Note that only the value of the last expression is returned.
{1, 2, 3}
and {1, 1, 2, 3}
Lists: [1, 1, 2, 3]
Sets cannot contain items of the same value while Lists can.
Mind that Collections are immutable: when you decide to change a List you need to create it anew.
[ 1 -> "Sunday", 2 -> "Monday"]
is a Map. In this case its of the type Map<Integer, String>.Evaluate the closure {x:Integer -> "Integer" + x}
The closure has an Integer input parameter called x
in the closure contexts: the closure a String, hence it is of the type { Integer : String }
We have been through some basic data types but there are other data types to explore and we will get gradually to all of them. Let's take a sneak peek and evaluate the following:
type(Integer)
returns the Integer type.Null
is a special data type with the sole value null
: all other types are super types of the Null type so any data type can all have the value null
.&var
is a reference to a variable.Note: We do not mention Records, which are complex data structures, and the related data types one of the reasons being that it is not possible to create a new Record type with the Expression Language since Records are modelled in diagrams similarly to processes.
In expressions, you can create local variables using the syntax def <TYPE> <VARIABLE_NAME>
.
Try this in REPL view:
Though the Boolean variable exists now, it has no value: its value is null
of the type Null
as we mentioned above. To assign a value to a variable, use the assignment operator :=
.
Local variables cannot be accessed from outside of their scope, expression block. An expression block is the expression, a loop, an if block, and an explicit block between the keywords begin
and end
.
Evaluate the following:
begin def Boolean boolVar := true end
If you need a variable that you can access from throughout the model or a particular namespace, use either global or local variables: however, these are part of modeling and we will deal with them in later courses on model resources.
The entire expression should look something like this:
def {List<Integer>:String} closureVar; closureVar := {myIntList:List<Integer> -> def String output; foreach Integer i in myIntList do output := output + i + ", " end; output }; def List<Integer> myIntList := 1..1000; closureVar(myIntList)
You can consider functions special types of closures: they take arguments of certain types and return a value of a certain type, plus they can be used whenever you would use a closure: They are sort of closure constants.
To call functions from an expression, use the syntax <FUNCTION_CALL>(<PARAMETER_1>, <PARAMETER_2>).
However, it is not possible to create a new function: you can only call existing function.
Note: We will create functions as part of model resources in another course.
Luckily, there are plenty of functions available in the Standard Library which is automatically available in your Module. And unluckily, you cannot call them from the Expression Evaluator REPL: you need to use the Expression Evaluator of a Model instance. Open the Expression Evaluator over a model instance:
"Hello!"
. This is simply a String literal, which surprisingly enough returns itself: "Hello!"
Hello!
. This is not a valid literal nor a variable name nor anything else that could be recognized as an expression: so what you have here is an invalid expression and the Evaluator tells you so.Let's try it out:
Creating a date value like d'yyyy-MM-dd HH:mm:ss.SSS'
is quite cumbersome: luckily there is a function that will make your life much easier: the date()
function. In the Expression Evaluator, start typing date
and press Ctrl + space to display the auto-completion dialog with the date functions. Click the one you like and define its parameters so it returns the correct date value.
Data types are arranged in a hierarchy which restricts relationships between data types. A type can be a subtype or a supertype of another type: If a type is a subtype of another type, it can be always used instead of the supertype, but not vice versa. Since the typing is so strict it prevents you from using incorrect types; for example, you cannot accidentally pass an Integer value to a String variable unless you explicitly cast it to a String.
For the types of the language, the most generic data type is the Object type: all data types are subtypes of the Object type: when a variable is of the Object type, you can assign it a value of any type.
In a complex type, one is a subtype of the other if their inner members are each other's subtypes: Map<KA, VA>; is subtype of Map<KB, VB>, if KA is a subtype of KB and VA is a subtype of VB.
Let's try it out:
In the Expression Evaluator of your model instance, create an Object variable, for example, def Object myObject
, and assign it a value of any type. To check the type of value the object holds, run typeOf(myObject)
. If you evaluate def Object myObject := [1,2,3]; typeOf(myObject)
, the expression will return the type List<Integer>
Otherwise, the data type structure of the built-in data types is pretty flat: there are only two other relationships:
def Collection<Object> myObject := [1,2,3]; typeOf(myObject)
def Collection<Object> myObject := {1,2,3}; typeOf(myObject)
If you want to change the value type, typically from a supertype to a subtype, you can use the build-in cast mechanism. Let's try it out: In the Expression Evaluator, evaluate the following expressions:
1 as Decimal
1.00 as Integer
1.12 as Integer
For details on features of the Expression Language, refer to the Expression Language Guide. As a reference, consider using the Expression Language Reference Card.