Previous Up Next

Chapter 11  Expressions

11.1  Blocks

Block
::= { (Statement ;)* Expression }
| { }
Statement
::= Expression
| Binding
Binding
::= let Patterns = Expression
| fun ValueId FunDef (and ValueId FunDef)*
Blocks are an expression form that combines the let-binding construct found in many functional languages with expression sequencing. A block consists of a sequence of zero or more statements followed by an expression. A statement is either a function or value binding, or a unit typed expression. The scope of a binding is from the binding to the end of the block. In the case of a function binding, the body of the function is included in the function's scope (to allow recursion). Mutually recursive functions are supported by joining the bindings with the keyword ``and.''

11.2  If-then-else expressions

Expression
::= if Expression then Expression else Expression
The if expression has the expected semantics. We require that the type of the first expression be Bool and that the types of the then and else clauses be the same.

11.3  TryExpressions

TryExpression
::= try Expression except MatchCase
| try Expression finally Expression

11.3.1  Try-except expressions

The try-except expression is used to handle exceptions (also see the discussion of exceptions in Section 6.3).

11.3.2  Try-finally expressions

The expression
try e1 finally e2
executes the expression e1 (called its body) until e1 terminates and then it executes e2 (called its finally clause). The finally clause will be excuted no matter how the body terminates (i.e., even if it raises an exception or exits the hosting thread). If the body (e1) terminates normally, then after the finally clause (e2) completes execution the result of the body is returned. If e1 terminates by raising an exception, then after the finally clause is executed, the exception packet is propagated to the next enclosing try-except or try-finally expression in the call-stack (see also the discussion of exceptions in Section 6.3). If the body (e1) terminates by exiting the hosting thread, then when the execution of the finally clause (e2) completes, control is passed to the next enclosing try-finally expression (if any). If the finally clause (e2) raises an exception, it is as if the whole try-finally expression had raised the exception.

The type of the whole expression is the type of the body and we require that the finally clause have unit type (i.e., return no results).
  G |- e1 |> t    G |- e2 |> ()  
  G |- try e1 finally e2 |> t  

The try-finally expression is used to robustly free resources. For example, will always close the stream s, even if the function application terminates without returning (e.g., if it raises an exception or calls exit).
{ val s = TextIO.openIn "somefile";
  try someFunction (s)
  finally TextIO.closeIn s
}
To illustract how try-finally and try-except expressions are unwound, consider the following example:
try {
  try {
    try {
      try {
        try raise BAR finally ConsoleIO.print "1";
        ConsoleIO.print "a"
      } finally ConsoleIO.print "2";
      ConsoleIO.print "b"
    } except { BAR => ConsoleIO.print "3" };
    ConsoleIO.print "c"
  } finally ConsoleIO.print "4";
  ConsoleIO.print "d"
} except { _ => ConsoleIO.print "5" }
This will print ``123c4d.'' But if we change the second finally clause to raise the exception BAZ as follows:
try {
  try {
    try {
      try {
        try raise BAR finally ConsoleIO.print "1";
        ConsoleIO.print "a"
      } finally {ConsoleIO.print "2"; raise BAZ};
      ConsoleIO.print "b"
    } except { BAR => ConsoleIO.print "3" };
    ConsoleIO.print "c"
  } finally ConsoleIO.print "4";
  ConsoleIO.print "d"
} except { _ => ConsoleIO.print "5" }
then the expression will print ``1245.'' Note that in both examples, the finally clauses always execute.
If we have some form of thread finalization, then the finally clauses should be executed when a thread is finalized. Also when a thread is alerted.

11.4  Raise expressions

Expression
::= raise Expression
The expression
raise e
evaluates e to an exception packet and then passes the packet to the dynamically innermost enclosing try-except or try-finally expression.

Because the raise expression does not return a value to its evaluation context, it can have any type.
  G |- e |> Exn    G |- t |> Ok  
  G |- raise e |> t  

11.5  Spawn expressions

Expression
::= spawn Expression
The spawn expression creates a new thread of control to execute the expression.

11.6  Sync expressions

Expression
::= sync Expression
The sync expression causes the hosting thread to synchronize on the synchronous event that the expression evaluates to.

11.7  Binary expression

Moby provides a fixed set of infix operators that can be used to form binary expressions. The infix operators are given in Table 11.1 (listed in order of increasing precedence).


Table 11.1: Moby infix and prefix operators

Infix operators
:= assignment operator (lowest precedence)
|| conditional or operator
&& conditional and operator
==  != equality operators
<=  <  >=  > relational operators
@  : list operators (right associativity)
<<  >>  >>> bitwise shift operators
| bitwise or operator
& bitwise and operator
+  - additive operators
*  /  % multiplicative operators
** exponentiation
^ function composition (highest precedence)

11.7.1  Assignment expressions

AssignmentExpr
::= ConditionalOrExpr := ConditionalOrExpr
| ConditionalOrExpr
The assignment operator is used to modify mutable storage. The left-hand side of an assignment must be an l-value; l-values include mutable fields in objects and records, array elements, and reference cells. The Moby typing rules distinguish between l-values and regular values as is discussed in Section 6.2. The := operator cannot be overloaded.

11.7.2  Conditional expressions

Moby provides two infix operators for conditional evaluation.
ConditionalOrExpr
::= ConditionalOrExpr || ConditionalAndExpr
| ConditionalAndExpr
ConditionalAndExpr
::= ConditionalAndExpr && EqualityExpr
| EqualityExpr
These operators are not strict in their right-hand-side argument and can be viewed as short-hand for if-expressions as follows:
e1 || e2 º if e1 then True else e2
e1 && e2 º if e1 then e2 else False
The || and && operators cannot be overloaded.

11.7.3  Equality expressions

EqualityExpr
::= EqualityExpr EqualityOp RelationalExpr
| RelationalExpr
EqualityOp
::= == | !=
Moby supports testing of equality on the following pervasive types:
Bool Char String Int
Long Integer Float Double
Extended
Equality is also defined on all enumeration types, object types, and record types with mutable fields. In addition, Array(t), Ref(t), and Ptr(t) are equality for any type t. In the case of mutable types (e.g., objects and arrays), two values are the same if they are the same memory object. For non-mutable equality types, two values are equal if they are the same value. The only exception to this is for the Float and Double types, which use the IEEE Std 754-1985 equality operation (i.e., x ¹ x when x is a NaN) [IEE85]. Note that unlike SML, equality does not extend to structured values and there is no notion of polymorphic equality.
We probably should allow equality of tuples of equality types.
We may want to support equality on monomorphic structured values in the future.

11.7.4  Relational expressions

RelationalExpr
::= RelationalExpr RelationalOp ListExpr
| ListExpr
RelationalOp
::= < | <= | >= | >
The relational operators are overloaded on the types: Bool, Int, Long, Integer, Float, Double, Char, and String.

11.7.5  List expressions

ListExpr
::= ShiftExpr ListOp ListExpr
| ShiftExpr
ListOp
::= @ | ::
Moby provides infix operators for consing an element on a list (::) and appending two lists (@). Unlike the other infix operators, the list operators associate to the right. The :: operator is actually the list-cell data constructor and can also be used in patterns.

11.7.6  Bitwise expressions

ShiftExpr
::= ShiftExpr ShiftOp BitwiseOrExpr
| BitwiseOrExpr
ShiftOp
::= << | >> | >>>
BitwiseOrExpr
::= BitwiseOrExpr | BitwiseAndExpr
| BitwiseOrExpr \/ BitwiseAndExpr
| BitwiseAndExpr
BitwiseAndExpr
::= BitwiseAndExpr & AdditiveExpr
| BitwiseAndExpr /\ AdditiveExpr
| AdditiveExpr
Moby provides shifting and bitwise logical operations on the Int, Long, and Integer types. As in Java, << is left-shift, >> is arithmetic right shift (i.e., with sign extension), and >>> is logical right shift (i.e., no sign extension). The infix operator | computes the bitwise logical or of its arguments, while the operator & computes the bitwise logical and. Moby defines two additional operators, \/ and /\, for use by libraries (e.g., for set union and intersection).

11.7.7  Arithmetic expressions

AdditiveExpr
::= AdditiveExpr AdditiveOp MultiplicativeExpr
| MultiplicativeExpr
AdditiveOp
::= + | -
MultiplicativeExpr
::= MultiplicativeExpr MultiplicativeOp Expr
| ExponentialExpr
MultiplicativeOp
::= * | / | %
ExponentialExpr
::= ExponentialExpr ** CompositionExpr
| CompositionExpr

The operators +, -, *, and /, are overloaded on all of the numeric types (Int, Long, Integer, Float, and Double) with their usual meaning. In addition, the + operator is used to denote string concatenation. The modulus operator (%) is overloaded on the integer types The exponentiation operator (**) is overloaded on the floating-point types (Float and Double).

11.7.8  Function composition expressions

CompositionExpr
::= PrefixExpr ^ CompositionExpr
| PrefixExpr
The right-associative operator ^ can be used to compose two functions. Because of the way that the Moby type system treats tuples, function composition is a special operator with its own typing judgement:
  G |- e1 |> (t''1, ..., t''k) ® (t'1, ..., t'n)    G |- e2 |> (t1, ..., tm) ® (t''1, ..., t''k)  
  G |- e1 ^ e2 |> (t1, ..., tm) ® (t'1, ..., t'n)  
Another way to think about this is that ^ is overloaded on all combinations of polymorphic functions, where the arities match up correctly.

11.8  Prefix expressions

PrefixExpr
::= PrefixOp PrefixExpr
| ApplicationExpr
PrefixOp
::= * | & | ! | ~ | -

11.9  Application expressions

ApplicationExpr
::= ApplicationExpr PostfixExpr
| PostfixExpr

11.10  Postfix expressions

PostfixExpr
::= PostfixExpr . Label
| PostfixExpr [ Expression ]
| AtomicExpr

11.11  Atomic expressions

AtomicExpr
::= fn FunDef
| case Expression of MatchCase
| ( (Expression (, Expression)*)opt )
| ( Expression : Type )
| ( Expression is Pattern )
| ( Expression isnot Pattern )
| ChoiceEvent
| Block
| new Pathopt MakerId
| self
| super . Label
| nack_event
| rdy_event
| ( Operator )
| Pathopt ValueId
| DataConstructor
| # DataConstructor
| ? DataConstructor
| Literal

11.11.1  Function expressions

11.11.2  Case expressions

11.11.3  Expression tuple

11.11.4  Constraint expression

11.11.5  Match test expression

It is possible to test if the value computed by an expression matches a variable-free pattern using the infix is and is not keywords. The semantics of these forms can be defined as follows:
(e is p) º case e of { p => True, _ => False }
(e isnot p) º case e of { p => False, _ => True }

11.11.6  Record expression

11.11.7  Choice event

ChoiceEvent
::= {| WrappedEvent (, WrappedEvent)* |}
WrappedEvent
::= Expression
| Expression \ Pattern => Expression
| Expression \ MatchCase

11.11.8  New expression

11.11.9  Self and super expressions

The self and super expressions are only allowed inside method bodies.

11.11.10  Identifier expressions


Previous Up Next