Expressions¶
This chapter specifies all ABS expressions. Expressions are usually evaluated for their value, although they can be used inside annotations as purely syntactic constructs that do not need to follow standard evaluation rules.
ABS expressions can either be pure or have side effects. Pure expressions only refer to entities in the functional and expression layer. They can be evaluated multiple times without influencing the execution of the imperative and object oriented layers of ABS. Pure expressions can be sub-expressions of other expressions and can be used as function bodies. Side-effect expressions, on the other hand, refer to semantic entities “above” the functional layer (objects and futures). They are only legal “stand-alone” (i.e., not as a sub-expression of another expression) and in certain places, mostly in the right-hand side of an assignment. This strict separation of pure and side-effect expressions simplifies the language, for example wrt. using expressions inside annotations and reasoning about expressions in static analysis tools.
Exp ::= |
PureExp | EffExp |
PureExp ::= |
SimpleIdentifier |
| |
|
| |
|
| |
|
| Literal |
|
| TemplateString |
|
| LetExp |
|
| DataConstrExp |
|
| FnAppExp |
|
| FnAppListExp |
|
| ParFnAppExp |
|
| WhenExp |
|
| CaseExp |
|
| OperatorExp |
|
| TypeCheckExp |
|
| TypeCastExp |
|
| |
|
EffExp ::= |
NewExp |
| SyncCall |
|
| AsyncCall |
|
| GetExp |
|
| AwaitExp |
Literals¶
Literals, as defined in Literals, are expressions.
Template strings¶
Template strings are strings allowing embedded expressions. In contrast to normal string literals, template strings can contain linebreaks and other special characters.
A template string starts and ends with a backtick (`) character.
A template string can contain zero or more pure expressions enclosed
by dollar signs ($). These expressions will be replaced by their
string representation (as with the function toString) every time
the template string is evaluated.
The only characters that need to be escaped by a backslash (\) in
a template string are the backtick itself (`) and the dollar sign
($). All other characters, including line breaks and the
backslash itself, do not need to be specially treated when writing a
template string.
TemplateString ::= |
|
{ |
Example:
`Hello $name$!
The price is \$$price$.`
(Note that the template string spans two lines.) The result of this expression will be a string with two lines, and with the values of the local variables name and price in place.
Operator expressions¶
ABS has a range of unary and binary operators working on pre-defined datatypes. All operators are pure (side effect-free).
OperatorExp ::= |
UnaryExp | BinaryExp |
UnaryExp ::= |
UnaryOp PureExp |
UnaryOp ::= |
|
BinaryExp ::= |
PureExp BinaryOp PureExp |
BinaryOp ::= |
|
The following table describes the meaning as well as the associativity and the precedence of the different operators. The list is sorted from low precedence to high precedence.
Expression |
Meaning |
Associativity |
Argument types |
Result type |
|---|---|---|---|---|
|
logical or |
left |
|
|
|
logical and |
left |
|
|
|
equality |
left |
compatible |
|
|
inequality |
left |
compatible |
|
|
less than |
left |
compatible |
|
|
less or equal |
left |
compatible |
|
|
greater than |
left |
compatible |
|
|
greater or equal |
left |
compatible |
|
|
concatenation |
left |
|
|
|
addition |
left |
number |
number |
|
subtraction |
left |
number |
number |
|
multiplication |
left |
number |
number |
|
division |
left |
number |
|
|
modulo |
left |
number |
number |
|
logical negation |
right |
|
|
|
integer negation |
right |
number |
number |
Semantics of comparison operators¶
ABS has generic equality and less-than comparison between two values of the same type.
Equality and inequality comparison is standard: by value for functional
datatypes and by reference for objects and futures. I.e., two strings
"Hello" compare as identical via ==, as do two sets containing identical
values. Two references to objects or futures compare as identical via == if
they point to the same object or future. The inequality operator !=
evaluates to True for any two values that compare to False under == and
vice versa.
For the comparison operator <, an ordering is defined in the following way.
Numbers compare as usual.
Strings compare lexicographically.
Algebraic datatypes (including Boolean) compare first by constructor name, then by comparing constructor arguments left to right.
Example:
Cons(_, _) < Nil Cons(1, _) < Cons(2, _)
Objects and futures are compared by identity, in an implementation-specific but stable way. This means that for any two variables
aandbthat point to different objects, the value ofa < bdoes not change as long asaandbare not re-assigned.footnote:[This ordering is not guaranteed to be stable between two invocations of a program. If ABS ever develops object serialization, care must be taken to uphold any datatype invariants across program invocations, e.g., when reading back an ordered list of objects.] The valuenullcompares less than any other object reference.
Let¶
The expression let T v = p in b evaluates b, with v bound
to the value of evaluating the expression p. The binding of v
introduced by the let-expression can shadow a binding of v outside
of the let expression.
More than one binding can be established in one let-expression. Bindings are evaluated sequentially, i.e., later bindings can use earlier variables in their value expression.
LetExp ::= |
|
{ |
|
|
Example:
let Int x = 2 + 2, Int y = x + 1 in y * 2 ①
((2 + 2) + 1) * 2)Data constructor expressions¶
Data Constructor Expressions are expressions that create data values by applying arguments to data type constructors. Data constructor expressions look similar to function calls, but data constructors always start with an upper-case letter.
For data type constructors without parameters, the parentheses are optional.
DataConstrExp ::= |
TypeIdentifier [ |
Example:
True
Cons(True, Nil)
Nil
Defining new data types and their constructors is described in Algebraic data types.
Function calls¶
Function calls apply arguments to functions, producing a value. Function call expressions look similar to data constructor expressions, but function names always start with a lower-case letter. The parentheses are mandatory in function calls.
FnAppExp ::= |
Identifier |
Example:
tail(Cons(True, Nil))
head(list)
N-ary function calls¶
Calls to n-ary Constructors (see N-ary constructors) are
written with brackets ([]) instead of parentheses (()).
FnAppListExp ::= |
Identifier |
Example:
set[1, 2, 3]
map[Pair(1, True), Pair(2, False)]
Partially-defined-function calls¶
Calls to partially defined functions (see Partial function definitions) are similar to function call expressions, but have an additional prepended set of function arguments.
ParFnAppExp ::= |
Identifier |
|
|
|
|
ParFnAppParam ::= |
Identifier | AnonymousFunction |
AnonymousFunction ::= |
|
|
Example:
map(toString)(list[1, 2]) ①
filter((Int i) => i > 0)(list[0, 1, 2]) ②
list["1", "2"]list[1, 2]Conditional expressions¶
The value of the conditional expression when c then e1 else e2 is either the
value of e1 or the value of e2, depending on the value of c, which must
be of type Bool. Depending on the value of c, either e1 or e2 is
evaluated, but not both.
WhenExp ::= |
|
Example:
when 5 == 4 then "Hmm" else "ok"
Case¶
ABS supports pattern matching via the Case Expression. A case expression consists of an input expression and one or more branches, each consisting of a pattern and a right hand side expression.
The case expression evaluates its input expression and attempts to match the resulting value against the branches until a matching pattern is found. The value of the case expression itself is the value of the expression on the right-hand side of the first matching pattern.
If no pattern matches the expression, a PatternMatchFailException is thrown.
There are four different kinds of patterns available in ABS:
Variables (with different semantics depending on whether the variable is bound or not)
Literal Patterns (e.g.,
5)Data Constructor Patterns (e.g.,
Cons(Nil,x))Underscore Pattern (
_)
CaseExp ::= |
|
CaseExpBranch ::= |
Pattern |
Pattern ::= |
|
| SimpleIdentifier |
|
| Literal |
|
| ConstrPattern |
|
ConstrPattern ::= |
TypeIdentifier [ |
Variable patterns¶
Variable patterns are written as identifiers starting with a lower-case letter. If the identifier does not name a variable in the current scope, the variable pattern matches any value and introduces a binding of the given identifier to the matched value for the right-hand side of the branch and the rest of the pattern itself. If, on the other hand, a variable with the given name is already in scope, its value is compared to the value being matched against.
The variable being named by the variable pattern can be used in the right-hand-side expression of the corresponding branch. Typically, pattern variables are used inside of data constructor patterns to extract values from data constructors. For example:
Example:
let (Pair<Int, Int> a) = Pair(5, 5) in
case a {
Pair(x, x) => x ①
| Pair(x, y) => y ②
} ③
Example:
let (x = 7) in
case Pair(5, 5) {
Pair(x, x) => x ①
| Pair(x, y) => y ②
| Pair(y, z) => z ③
} ④
Literal patterns¶
Literals can be used as patterns. The pattern matches if the value of the case expression is equal to the literal value.
Example:
let (Pair<Int, Int> a) = Pair(5, 5) in
case a {
Pair(3, x) => x ①
| Pair(x, y) => y ②
} ③
Data constructor patterns¶
A data constructor pattern is written like a standard data constructor expression. Constructor arguments are again patterns.
Example:
let (List<Int> l) = list[1, 2, 3] in
case l {
Nil => 0 ①
| Cons(1, _) => 15 ②
| Cons(_, Cons(y, _)) => y ③
} ④
The wildcard pattern¶
The wildcard pattern, written with an underscore (_) matches any
value.
Example:
let (List<Int> l) = list[1, 2, 3] in
case l {
Nil => True ①
| _ => False ②
}; ③
The wildcard pattern can be used as the last pattern in a case expression to define a default case.
Typing of case Expressions¶
A case expression is type-correct if and only if all its expressions and all its branches are type-correct and the right-hand side of all branches have a common supertype. This common supertype is also the type of the overall case expression. A branch (a pattern and its expression) is type-correct if its pattern and its right-hand side expression are type-correct. A pattern is type-correct if it can match the corresponding case expression.
Type-check expressions¶
Variables pointing to objects are typed by interface, which means that the concrete class of the referenced object might support more methods than can be called through the reference. The type-check expression checks if an object implements a given interface.
TypeCheckExp ::= |
PureExp |
Example:
interface I {}
interface J {}
class C implements I, J {}
{
I o = new C();
if (o implements J) { ①
println("o is a J");
}
}
True, since C implements both I and
J even though they are distinct types.Type-cast expressions¶
Variables pointing to objects are typed by interface, which means that
the concrete class of the referenced object might support more methods
than can be called through the reference. The type-cast expression
returns a reference of type I to the same object if it implements
the given interface I, or null otherwise.
TypeCastExp ::= |
PureExp |
Example:
interface I {}
interface J {}
class C implements I, J {}
class D implements I {}
{
I o = new C();
J j = o as J; ①
I o2 = new D();
J j2 = o2 as J; ②
}
j will be non-null since C implements J.j2 will be null since D does not implement J.New¶
A new expression creates a new object from a class name and a list
of arguments. In ABS, objects can be created in two different ways.
Either they are created in the current COG, using the new local
expression, or they are created in a new COG by using the new
expression (see The ABS actor and concurrency model for more details about
cogs).
Note
This expression is a side-effect expression and cannot be used as a sub-expression.
NewExp ::= |
|
Example:
new local Foo(5)
new Bar()
Classes can declare an init block (see Classes), which is
executed for each new instance. The semantics of the new
expression guarantee that the init block is fully executed before the
new object begins receiving method calls. Classes can also declare a
run method, which is asynchronously invoked after the init block
and subject to the normal scheduling rules for processes.
Synchronous method calls¶
A synchronous call consists of a target expression evaluating to an interface type, a method name declared in that interface, and a list of argument expressions.
Note
This expression is a side-effect expression and cannot be used as a sub-expression.
SyncCall ::= |
PureExp |
Example:
Bool b = x.m(5, 3);
The semantics of the synchronous method call differ depending on whether the caller and callee are in the same cog. A synchronous method call between objects in the same cog has Java-like semantics, i.e., the caller is suspended and the called method starts executing immediately. When the called method finishes, the caller process is scheduled and resumes execution.
However, if caller and called object are in different cogs, a
synchronous method call is equivalent to an asynchronous method call
immediately followed by a get expression on the resulting future.
This means that the intuitive semantics of synchronous method calls
are preserved, but introduces the possibility of deadlocks in case the
callee tries to call back to an object of the caller cog.
Asynchronous method calls¶
An asynchronous call consists of a target expression evaluating to an interface type, a method name declared in that interface, and a list of argument expressions.
Note
This expression is a side-effect expression and cannot be used as a sub-expression.
AsyncCall ::= |
PureExp |
An asynchronous method call creates a new task in the COG that
contains the target. This means that the caller task proceeds
independently and in parallel with the callee task, without waiting
for the result. The result of evaluating an asynchronous method call
expression o!m(e) is a _future_ of type (Fut<V>), where V
is the return type of the callee method m.
This future is resolved (i.e., it gets a value) when the callee task finishes. It can be used to synchronize with the callee task and obtain the result of the method call.
Example:
Fut<Bool> f = x!m(5);
Get¶
A get expression is used to obtain the value from a future. The current task is blocked until the future has been resolved, i.e., until either the return value is available or an exception has occurred in the callee task. No other task in the COG can be activated while the current task is blocked by a get expression.
Note
This expression is a side-effect expression and cannot be used as a sub-expression.
GetExp ::= |
PureExp |
Example:
Bool b = f.get;
If the future contains a normal return value, the value of the get expression is that value. If the future contains an exception thrown by the callee process, evaluating the get expression will throw the same exception. The value thrown by a get expression can be caught by try-catch as normal (see Handling exceptions with try-catch-finally).
The following example assigns the return value contained in f to the
variable b. In case of any error, b is assigned False.
try b = f.get; catch { _ => b = False; }
Await (expression)¶
An await expression is a way to asynchronously call a method, wait for the callee to finish, and optionally get the result in one expression.
Note
This expression is a side-effect expression and cannot be used as a sub-expression.
AwaitExp ::= |
|
Example:
A x = await o!m();
The statement above is equivalent to these three statements:
Fut<A> fx = o!m();
await fx?;
A x = fx.get;
Exception propagation and the await expression¶
As explained in Section Get, exceptions propagate from
callee to caller via the get expression. An await statement
will proceed once the callee process has finished, but an exception in
the future will not be raised when executing the await statement.
To align the await expression with that behavior, an exception
will only be raised when the return value of a method call is used,
e.g., by assigning it to a variable. Hence the following line of code
will not raise an error even if the call to o!m() results in an
exception:
await o!m();
Since the return value is ignored in the statement above, it is
equivalent to these two statements (note the absence of a get
expression`):
Fut<A> fx = o!m();
await fx?;
Destiny (expression)¶
Note
This feature is only available in the Erlang backend.
The destiny expression returns the future of the asynchronous call
that is currently being executed. It returns a value of type
Destiny. For more information on how Destiny values can be
used, see The destiny type.
_DestinyExp_ ::= |
|
Example:
class C {
Destiny myMethod() {
return destiny;
}
Unit run() {
Fut<Destiny> f = this!myMethod();
await f?;
Destiny g = f.get;
assert(f == g); ①
}
}
True since the returned
destiny value is the same as the future of the asynchronous call
to myMethod.If destiny is evaluated as part of a synchronous call S then it evaluates to
the future of the task that is executing S:
Example:
class C {
Unit syn(Destiny caller) {
assert(destiny == caller);
}
Unit asyn() {
this.syn(destiny);
}
Unit run() {
this!asyn();
}
}
Note
There is also a destinyOf built-in function that can
retrieve the future of the asynchronous call that is
currently being executed by a Process, see
Processes and process attributes.