Business models created in LSPS use the LSPS Expression Language, a statically typed language with support for functional and object-oriented programming, to define value of properties, conditions, variable, assignments, etc.
The basic unit in the language is an expression, a piece of code with operators, literals, and keywords of the language. Each expression returns a single value, for example:
3+2
returns 5
;def String var := "hello"
returns "hello"
, the value of the right side of the expression;To get the idea, evaluate expressions in the Expression Evaluator REPL view: To display it in your PDS, go to Window > Show View > Other and in the displayed dialog search for the view.
This is all fine but an expression is a little bit too simple to accommodate all the logic that we might need to define say element properties: That is why definitions of properties, assignments, etc. can hold an expression block– a grouping of expressions. In REPL, you are working within such an expression block. To clear the block, click Clear Output and Variables button.
In a block, you can chain expressions with a semicolon ;
(the last expression does no require a semicolon). This allows you to use the expression variables: such variables must have a unique name within the given block. Mind also that they are visible within the block and its child blocks, while remaining invisible to any parent blocks. The return value of an expression block is the value returned by its last expression (comments are marked with //
and are ignored):
Expression blocks can make use of expression variables: to declare such a variable, we use the def
keyword with the syntax def <TYPE> <VARIABLE_NAME>
).
//creates an expression variable logMessage //and assigns it the value "hello": def String logMessage := "hello "; //appends "world!" to the logMessage vvariable: logMessage + "world!" //returns `"hello world!"`:
You can create an expression block explicitly with the begin
and end
keywords.
This expression block returns 3
:
begin def Integer var := 3; begin var := 5 end; var; end
while this expression block fails to return a value:
begin def Integer var := 3; begin var := 5 end; var; end; var;
getYear()
returns the year from a date: Note: When evaluating your expression in the Expression Evaluator REPL view, the expressions are evaluated outside of any model: under normal circumstances, on runtime, expressions are evaluated in a context of their model instance so they can access the data of the model instance, such as, when and by whom the instance was created. Therefore definitions from your resources and definitions that require execution such contexts, such as,
getCurrentPerson()
orgetCurrentTodo()
, are not allowed in REPL. If you want to know more about contexts, refer to namespaces and contexts.
Let's go through the operators and data types of the Expression Language:
1 + 1
1 + 1.12
1/1.1
1%1.1
3*3
3/3
3**3
"Hello, " + "world!"
"Hello, number " + 1
"1" + 1
1 + "hello"
This results in an invalid expression since the + operator that has Integer and String as its input does not exist, while "" + 1 + "hello"
will evaluate just fine.
"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"
d'2015-12-24 20:00:00.000' < d'2017-12-24 20:00:00.000'
true and false
true and true
true or false
false or true
true xor false
!true
"This will not return!";"Hello, " + #10 + "world!";
"This will not return!";"Hello, " + #10 + "world!"
Note that only the value of the last expression is returned.
"This will not return!" "Hello, " + #10 + "world!"
: this is invalid.{1, 2, 3}
and {1, 1, 2, 3}
[1, 1, 2, 3]
[ 1 -> "Sunday", 2 -> "Monday"]
is a Map, more specifically Map<Integer, String>.
Mind that Collections and Maps are immutable:
def Set<Integer> mySet := { 1, 2, 3 }; mySet.add(4); mySet
The return value is { 1, 2, 3}
: The set with 4
was returned by mySet.add(4)
, but the value has not been assigned to anything. To change the content of a Collection, create a new one and resign.
mySet := mySet.add(4) = {1,2,3,4}
Evaluate the closure {x:Integer -> "Integer" + x}
The closure has an Integer input parameter called x
in the closure contexts: the closure a String: It is of type { Integer : String }
; However, it doesn't do much, since we cannot call it:
def {Integer:String} closureVar := {x:Integer -> toString(x)};
closureVar(5)
Important: It is recommended to always explicitly define the type of the closure inputs, in the example, we explicitly define that x is an Integer in
x:Integer
.
type(Integer)
returns the Integer Type, the Type being a data type.&var
is a reference to a variable. instanceof
operator of the Expression Language: "hello" instanceof String;
isInstance()
of the Standard Library: isInstance(3, Integer);The difference between the built-in and the function is that the built-in cannot take an expression as as its right side:
def Type<Object> stringType := String; "hello" instanceof stringType;//does not evaluate isInstance("hello", stringType);
Note: Along with a few other types, we have omitted the Record type, which is the parent of the Record types (Records): Records are complex data structure types that can contain multiple fields of non-complex data types (fields cannot be of a Record type) and be related to each other. They serve to represent your business data. While instances of Records are created in the Expression Language, it is not possible to create Record types themselves in the Expression Language since Records are modelled using the Modeling Language. You will model Records later.
Now, do the following:
The result 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)
A function is a set of instructions that can take arguments, perform the instructions and return a value: the return value will generally depend on the arguments.
Unlike closures, functions cannot be defined just in any expression block. You will define a custom function later. For now, we will use the functions provided by the Standard Library. To call functions, use the syntax <FUNCTION_CALL>(<ARG_1>, <ARG_>).
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 simpler: In the REPL view, start typing date
and press Ctrl + space to display the auto-completion menu. Click on the date
function you like and define its parameters so it returns the correct date value.
If a function has one required parameter, it can be defined as an extension method: this means that you can call it with the syntax:
Hence, instead of writing toString(myInteger)
, you can write myInteger.toString()
, which is more convenient, since you can use autocompletion options of an Integer.
To find out, if a function of the Standard Library is an extension method, go to its declaration (click the function call while holding the CTRL key) and look for @Extensionmethod
annotation or Extension method flag, depending on whether the function definition file uses the text or graphical editor.
Here, we check if the function definition has an Extension method flag and then rewrite the function call to an equivalent extension method call:
Data types constitute a hierarchy: A type can be a subtype or a supertype of another type. If a type is a subtype of another type, it can be used instead of the supertype, but not vice versa. The strict typing prevents usage of incorrect types; for example, you cannot accidentally pass a Date 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 complex type is a subtype of another complex type 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 REPL view, create an Object variable, for example, def Object myObject
, and assign it a value of any type. To check the type of the value in the object, run typeOf(myObject)
. The expression def Object myObject := [1,2,3]; typeOf(myObject)
, returns type List<Integer>
Otherwise, the data type structure of the built-in data types is pretty flat:
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 have the value null
.Person.name
).Let's try it out; Evaluate the following:
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:
1 as Decimal
1.00 as Integer
1.12 as Integer
Note that similarly to the case of instanceof
vs isInstance()
, there is operator as
and the cast()
function of the Standard Library. The as
operator cannot take an expression as as its right side:
def Integer i := 1; = 1 typeOf(i) = Integer 123.00 as typeOf(i) = no viable alternative at input '(' cast(123.00, typeOf(i)) = 123
For details on the Expression Language, refer to the Expression Language Guide. For quick reference, use the Expression Language Reference Card.
Proceed to Models.