1.2. Complex Expressions

Complex expressions recursively contain other expressions. They may be formed in a number of ways: using one of Orc's four combinators, adding a declaration, adding a conditional expression, or using an expression as an operand or site call argument.

1.2.1. Combinators

The concurrency combinators are one of the two fundamental concepts of Orc programming, the other being sites. They provide the core orchestration capabilities of Orc: parallel execution, sequential execution, blocking on future values, terminating a computation, and trying an alternative if some computation halts.

Parallel

Orc's simplest combinator is |, the parallel combinator. Execution of the complex expression F | G, where F and G are Orc expressions, executes F and G concurrently. Whenever a value is published during the execution of F or G, the execution of F | G publishes that value. Note the publications of F and G are interleaved arbitrarily.

{- Publish 1 and 2 in parallel -}  

1 | 1+1

{-
OUTPUT:PERMUTABLE
1
2
-}

The brackets {- -} enclose comments, which are present only for documentation and are ignored by the compiler.

Sequential

Now that we have expressions which publish multiple values, what can we do with those publications? The sequential combinator, written F >x> G, combines the expression F, which may publish some values, with another expression G, which will use the values as they are published; the variable x transmits the values from F to G.

The execution of F >x> G starts by executing F. Whenever F publishes a value, a new execution of G begins in parallel with F (and with other executions of G). In that instance of G, variable x is bound to the value published by F. Values published by the executions of G are published by the whole expression, but the values published by F are not published by the whole expression; they are consumed by the variable binding.

{- Publish 1 and 2 in parallel -}
  
(0 | 1) >n> n+1

{-
OUTPUT:PERMUTABLE
1
2
-}

{- Publish 3 and 4 in parallel -}
  
2 >n> (n+1 | n+2)

{-
OUTPUT:PERMUTABLE
3
4
-}

{- Publish 5 -}
  
2 >x> 3 >y> x+y

{-
OUTPUT:
5
-}

The sequential combinator may also be written without a variable, as in F >> G. This has the same behavior, except that no variable name is given to the values published by F. When F publishes only one value, this is similar to a sequential execution in an imperative language. For example, suppose we want to print three messages in sequence:

{- Print three messages in sequence -}

Println("Yes") >>
Println("We") >>
Println("Can") >>
stop

{-
OUTPUT:PERMUTABLE
Yes
We
Can
-} 

The simple expression stop does nothing and halts immediately. In conjunction with >> , it can be used to ignore unneeded publications, such as the signal that would be published by Println("Can").

Pruning

The pruning combinator, written F <x< G, allows us to block a computation waiting for a result, or terminate a computation. The execution of F <x< G starts by executing F and G in parallel. Whenever F publishes a value, that value is published by the entire execution. When G publishes its first value, that value is bound to x in F, and then the execution of G is immediately killed. A killed expression cannot make any more site calls or publish any values.

During the execution of F, any part of the execution that depends on x will be blocked until x is bound. If G never publishes a value, parts of F may be blocked forever.

{- Publish either 5 or 6, but not both -}

x+2 <x< (3 | 4)

{-
OUTPUT:
5
-}
{-
OUTPUT:
6
-}

Though a terminated execution may not make any new calls, the calls that it has already made will continue normally; their responses are simply ignored. This may have surprising consequences when a call has side effects, as in the following example.

{- This example might actually print both "uh" and "oh" to the
   console, regardless of which call responds first. -}

stop <x< Println("uh") | Println("oh")

{-
OUTPUT:PERMUTABLE
uh
oh
-}
{-
OUTPUT:
uh
-}
{-
OUTPUT:
oh
-}

Both of the Println calls could be initiated before either one of them publishes a value and terminates the expression. Once the expression is terminated, no new calls occur, but the other Println call still proceeds and still has the effect of printing its message to the console.

Otherwise

Orc's fourth concurrency combinator, the otherwise combinator, is written F ; G. The execution of F ; G proceeds as follows. First, F is executed. If F halts, and has not published any values, then G executes. If F did publish one or more values, then G is ignored.

1.2.2. val

An expression may be preceded by one or more declarations. Declarations are used to bind values to be used in that expression (or scope).

The declaration val x = G, followed by expression F, executes G, and binds its first publication to x, to be used in F.

This is actually just a different way of writing the expression F <x< G. Thus, val shares all of the behavior of the pruning combinator: F executes in parallel with G, uses of x block until G has published, and when G publishes, it is killed. In fact, the val form is used much more often than the <x< form, since it is usually easier to read.

1.2.3. Conditional Expressions

Orc has a conditional expression, written if E then F else G. The else branch is required. Execution of if E then F else G first executes E. If E publishes true, E is terminated and F executes. If E publishes false, E is terminated and G executes.

1.2.4. Nested Expressions

The execution of an Orc expression may publish many values. What if we want to use such an expression in a context where only one value is expected? For example, what does 2 + (3 | 4) publish?

Whenever an Orc expression appears in such a context, it executes until it publishes its first value, and then it is terminated. The published value is then used in the context. This allows any expression to be used as an operand of an operator expression or an argument to a site call.

{- Publish either 5 or 6 -}

2 + (3 | 4)

{-
OUTPUT:
5
-}
{-
OUTPUT:
6
-}

{- Publish exactly one of 0, 1, 2 or 3 -}

(0 | 2) + (0 | 1)

{-
OUTPUT:
0
-}
{-
OUTPUT:
1
-}
{-
OUTPUT:
2
-}
{-
OUTPUT:
3
-}

To be precise, whenever an Orc expression appears in such a context, it is treated as if it were on the right side of a pruning combinator, using a fresh variable name to fill in the hole. This is called deflation.