LSPS documentation logo
LSPS Documentation
Data Types

The LSPS Expression Language is a statically typed language: all values must define their data type before they can be used on runtime. The data type determines what value the object can hold.

When you declare a local string variable (def String s), an object that can hold a reference to a string is created in the memory.

Data types can inherit properties from each other: one type becomes the supertype of another type: An object of a data type can be used anywhere where you can use its super type, for example, when assigning values, sending function arguments, etc. For example, you can assign a variable of the type Decimal also a value of the type Integer, since Integer is a subtype of Decimal. These relationships apply to built-in as well as custom data types in a hierarchy.

dataTypeHierarchy.png
Built-in Data Type Hierarchy
All data types are part of the data type hierarchy with the Object data type as the root of the tree.

Data Type Mapping

The data types are implemented as Java or LSPS classes. This information is useful when you need to pass them to your Java code.

Expression Language Type Class
String java.lang.String
Boolean java.lang.Boolean
Binary com.whitestein.lsps.lang.exec.BinaryHolder
Decimal com.whitestein.lsps.lang.Decimal
Integer com.whitestein.lsps.lang.Decimal
Date java.util.Date
Reference com.whitestein.lsps.lang.exec.ReferenceHolder
Collection com.whitestein.lsps.lang.exec.CollectionHolder
List com.whitestein.lsps.lang.exec.ListHolder
Set com.whitestein.lsps.lang.exec.SetHolder
Type com.whitestein.lsps.lang.type.Type
Map com.whitestein.lsps.lang.exec.MapHolder
Closure com.whitestein.lsps.lang.exec.ClosureHolder
Record com.whitestein.lsps.lang.exec.RecordHolder
Enumeration com.whitestein.lsps.lang.exec.EnumerationImpl
Property com.whitestein.lsps.lang.exec.Property
Object java.lang.Object

Casting

Casting takes place when you change the type of a value to another type. When "widening" a type, that is changing a value of a subtype to its supertype, the type change occurs automatically. When "narrowing" a type, you need to cast the type explicitly:

<objectName> as <newObjectType>
person as NaturalPerson

Note: Alternatively, you can use the cast() function of the Standard Library, for example, cast(o, type(D))

Object

The Object data type is the super type of all data types and therefore the only data type without a supertype. It is represented by java.lang.Object. Therefore if you want to allow any data type, for example, as a parameter, use the Object data type. It is represented by the java.lang.Object class. All objects can hold the null value.

Simple Data Types

Simple data types contain values without further data type structuring.

Binary

Objects of binary data type are typically used to hold binary data when downloading and uploading files, or working over binary database data.

It is represented by com.whitestein.lsps.lang.exec.BinaryHolder.

The Binary literals are not supported.

To define the type:

Binary

The Binary data type serves to work with binary data typically from another resource, typically, pictures, videos, etc.: You could define a Field of a Record as being of the Binary type and populate it with picture data.

String

A String holds a sequence of Unicode characters. The data type is implemented by the java.lang.String class.

To define the type:

String

To create an instance:

"Sequence of Unicode characters"

A String can contain special characters defined using their ASCII codes. For example, you can use the ASCII tab code (#9) to have a tab in your String, line feed (#10) to make a multi-line String, etc., for example:

"This" + #10 + "is" + #10 + "a" + #10 + "multi-line" + #10 + "string with "+
"multiple" + #10 + " new lines " + #10 + " which represent " + #10 + "line breaks."

To escape characters, use the double-quote (") character:

"This is all one string: ""Welcome to String escaping!"""

To annotate a string that is not to be localized, add the hashtag # sign in front of the string.

#"This is a string value which will not be localized."

The hashtag # sign signalizes that the String is to remain unlocalized and that this was the intention of the developer. Such Strings are excluded from checks of unlocalized Strings.

To create a local variable:

def String s := "My String"

Boolean

Boolean objects hold the values null, true or false. It is implemented by the java.lang.Boolean class.

To define the type:

Boolean

To create an instance:

true

To create a local variable:

def Boolean s := true

The expression def Boolean boolVar := true declares and defines a local variable of the type.

Integer

Integer objects hold natural numbers and their negatives. The data type is a subtype of the Decimal type. It is represented by com.whitestein.lsps.lang.Decimal.

Note that the underlying Java type is BigDecimal; hence no relevant maximum limit applies to the value.

To define the type:

Integer

To create an instance:

-100

To create a local variable:

def Integer i := 42

Decimal

Decimal objects hold numerical fixed-point values. It is represented by com.whitestein.lsps.lang.Decimal.

Note: A Decimal type is internally represented by two integer values: an unscaled value and a scale. The value is hence given as <UNSCALED_VALUE>*10**<SCALE_VALUE>. The integer scale defines where the decimal point is placed on the unscaled value. The scale is a 32-bit integer. If zero or positive, the scale is the number of digits to the right of the decimal point. If negative, the unscaled value is multiplied by ten to the power of the negation of the scale. Decimal values are, for example, 2e+12, -1.2342e0, 1.0.

When assigning a value of the type Decimal, you need to define the scale and rounding mode. The following rounding modes can be used on decimals:

  • UP: rounds away from zero
  • DOWN: rounds towards zero
  • CEILING: rounds towards positive infinity
  • FLOOR: rounds towards negative infinity
  • HALF_UP: rounds towards the nearest neighbor unless both neighbors are equidistant, in which case it rounds up
  • HALF_DOWN: rounds towards nearest neighbor unless both neighbors are equidistant, in which case it rounds down
  • HALF_EVEN: rounds towards the nearest neighbor unless both neighbors are equidistant, in which case, it rounds towards the even neighbor
  • UNNECESSARY: asserts that no rounding is necessary

The rounding mode is defined for decimal variables or for a record field of the type Decimal.

To define the type:

Decimal

To define a Decimal type with Scale 100 and Rounding Model UP:

Decimal(100, UP)

To create an instance:

-10.0;
6.63E-34

To create a local variable:

def Decimal intVar := 100

Important: Decimal values are normalized if they contain 0 digits after the decimal point: decimal value 1.0 and integer value 1 have the same numerical value and therefore 1.0 == 1.

Date

The Date object holds a specific time. It is represented by java.util.Date.

Important: Only dates since the introduction of the Gregorian calendar, that is, the year 1582 are supported: for Dates that occurred before, a shift in days can occur rendering the date incorrect.

To define the type:

Date

To create an instance:

d'yyyy-MM-dd HH:mm:ss.SSS'

To create a local variable:

def Date myDate := d'2015-12-24 20:00:00.000'

Important: When constructing a date, consider using the date functions from the Standard Library.

Complex Data Types

Complex data types are composite data types based on other data types.

Collections

A Collection is a groupings of items of a particular data type. It is represented by com.whitestein.lsps.lang.exec.CollectionHolder.

Collections are ordered and immutable: once a collection is created, it cannot be changed, while you can change individual collection items. Each item of the collection represents an expression

To access an element of a Collection, you need to specify the position of the element in the List. Note that the first element of a List is on position zero. For example, [10,20,30][1] returns 20 as position 0 of this list points to the value 10, the first element of the list.

Hierarchy of collections follows the hierarchy of their elements: List<TA> is a subtype of List<TB>, if TA is a subtype of TB. Set<TA> is a subtype of Set<TB>, if TA is a subtype of TB.

To define the type:

Collection<T>

List

A List represents an ordered collection of items of a type with possible duplicate values. It is represented by the com.whitestein.lsps.lang.exec.ListHolder class.

To define the type:

List<T>

To create an instance:

["a", "b", "c", "d"]

List of lists

[["a", "b"], ["b", "c", "a"]]

To access an item in a List: Specify the position of the item in the List starting from 0: For example, [10,20,30][1] returns 20 as position 0 points to the value 10, the first element.

Unlike on Sets, accessing list items is performance-wise efficient.

Creating a List of Integers with the Range Operator

To create a List of Integers, you can use the .. operator, the range operator:

1..5
//is equivalent to [1, 2, 3, 4, 5]
3..1
//is equivalent to [3, 2, 1]

Set

A Set represents an ordered collection of items of a type with no duplicate values. It is represented by the com.whitestein.lsps.lang.exec.SetHolder class.

To define the type:

Set<T>

To create an instance:

//Set<Integer>:
{1,2,3};

To access an item in a Set: Specify the position of the item in the Set starting from 0: For example, {10,20,30}[1] returns 20 as position 0 points to the value 10, the first item.

Unlike in Lists, accessing items is performance-wise inefficient.

Map

Maps hold keys with their values and are immutable: once a map is created, it cannot be changed. You can change its key-value pairs, however, the map itself cannot be modified.

It is represented by com.whitestein.lsps.lang.exec.MapHolder.

To define the type:

//type map with the K type of its keys and the V type of its values:
Map<K, V>

Note that hierarchy of maps follows the hierarchy of its key and value types: Map<KA, VA> is a subtype of Map<KB, VB>, if KA is a subtype of KB and VA is a subtype of VB.

To create an instance:

[1->"a", 2->"b"]

To initialize an empty map, use the empty-map operator [->]:

def Map<Object, Object> myEmptyMap := [->];

To get a value of a key, specify the key for the appropriate value in square brackets, for example, ["firstKey"->"a", "anotherKey"->"b"]["anotherKey"] returns the string "b".

Reference

A Reference holds a reference expression that resolves to a variable or a record field (slot), not their value.

A Reference is conceptually similar to pointers in other languages. However, a Reference points to a variable or a record field, not to memory slot.

  • A Reference to a variable resolves to the variable object
  • A Reference to a record field resolves to the record instance and the association path.

The data type is represented by com.whitestein.lsps.lang.exec.ReferenceHolder.

To define the Reference type:

Reference<T>

To create a Reference instance, use the & reference operator:

&<TARGET>

**To get the value in the referenced slot, use the * dereference operator.

Example Use of Reference

//creates new Patient record instance with the diagnosis "flu":
def Patient r := new Patient(diagnosis -> "flu");
 
//creates the local variable status that holds the reference to the diagnosis field of the Patient record instance:
def Reference<String> status := &(r.diagnosis);
 
//function sets the status to cured on the patient:
setStatusToCured(&r.status);
 
//implementation of the setStatusToCured() function:
//def Reference<String> status:= statusReference;
//*status:="cured";

Closure

A closure is an anonymous function that can declare and make use of local variables in its own namespace and use variables from its parent expression context as opposed to lambdas. It is represented by com.whitestein.lsps.lang.exec.ClosureHolder.

To define the type:

//Syntax: { <INPUT_TYPE_1>, <INPUT_TYPE_2> : <OUTPUT_TYPE> }
{ String : Integer}
//Closure that that has no parameters and returns an Object:
{ : Object}

Subtyping in closures is governed by their parameters and return type: Closure A is a subtype of closure B when:

  • the return type of closure A is a subtype of the return type of closure B
  • and parameter types of closure B are subtypes of parameter types of closure A.

{ S1,S2,... : S } is subtype of { T1, T2,... : T } when T1 is subtype of S1, T2 is subtype of S2, etc. and S is subtype of T.

To create an instance:

//Syntax: { <PARAMETERS> -> <CLOSURE_BODY> }
{s:String -> length(s)}

Parameter types can be omitted:

{s -> length(s)}

The system attempts to infer the type of closure arguments and its return value. Note that the types might be resolved incorrectly. To prevent such an event, consider defining the argument type explicitly as in the example.

To evaluate a closure use the parentheses () operator with the closure arguments:

def {Integer:String} closureVar := {x:Integer -> toString(x)};
def String closureResult := closureVar(3);

User-Defined Record

A user-defined Record is the subtype of the Record type. It serves to create custom structured data types. A Record can define relationships with another Records.

It is not possible to declare a Record type in the Expression Language: Records are modeled in the data-type editor of the Living Systems® Process Design Suite. However, you can create instances of Records. When you pass a Record, for example, as a function argument, it is passed by reference.

It is represented by com.whitestein.lsps.lang.exec.RecordHolder.

To create an instance:

new <RECORD>(<NAME_OF_FIELD> -> <FIELD_VALUE>, <NAME_OF_FIELD> -> <VALUE>, ...))

For example:

new Book(genre -> Genre.FICTION, title -> "Catch 22", author -> new Author(name -> "Heller, Joseph"))

Use the Dot operator to access Record fields or related records and fields.

Example: def declares a new variable of the MyRecord type. new creates a new MyRecord instance, which is assigned to the variable (the instance is the proper memory space with the record) . Variable r points to the MyRecord instance and it is returned by the expression.

def MyRecord r := new MyRecord(recordField -> "value")

Function call with a new Record instance as its argument:

acquireApproval(new Approval(outcome -> "permitted"))

To access fields of a Record, use the access operator .:

book.title

Note that the dot operator . fails with an exception if the <EXPRESSION> with the access operator is null. Use the safe-dot operator to prevent the exception.

When accessing a field or record that is of the type Reference, the property is automatically dereferenced. Therefore the expression (*ref).fieldName and ref.fieldName are identical.

Property

The Property data type holds the type of a Field in a user-defined Record.

Example

def Property authorNameField := Author.firstName;

Type

The Type data type holds data types, for example, a Type can hold the value String data type, a particular Map data type, a Record data type, etc. It is represented by com.whitestein.lsps.lang.type.Type.

The Type data type can be used to check if object are of a particular data type. The output can be then further used when Casting the object.

To define the type:

Type<T>

To create an instance:

//type is a keyword:
type(<TYPE>)

Example Usage

switch typeOf(person)
  case type(NaturalPerson) -> getFullName(person as NaturalPerson)
  case type(LegalPerson) -> getFullName(person as LegalPerson)
  case type(PersonGroup) -> getFullName(person as PersonGroup)
  default -> person.code + #" <Unknown type>"
end

Enumeration

An Enumeration is a special data type that holds literals. It is represented by com.whitestein.lsps.lang.exec.EnumerationImpl.

You cannot create an Enumeration type in the Expression Language directly: It is modeled as a special Record type.

To define the type:

<ENUMERATION>

To create an instance:

<enumeration_name>.<literal_name>

Example:

//Weekday is an enumeration with values MONDAY, TUESDAY, etc.
def Weekday wd := Weekday.WEDNESDAY

You can compare enumeration literals with the comparison operators =, !=, <, >, <=, >= for enumerations of the same enumeration type. Enumeration literals are arranged as a list of values; hence the comparison is based on comparing their indexes: the order depends on the order of the literal as modeled in the Enumeration.

Null

The Null data type signalizes an unspecified value: its only value is null.

The data type is the subtype of every other data type, so that any object can take the null value.

To define the type:

Null