Parallel composition operators

A real power of SubScript can be seen when working with processes that are running in parallel with each other. SubScript has a considerable amount of operators that define how to run processes in parallel.

The operands of such operators are code blocks or whole scripts that need running. The normal code blocks are executed atomically by SubScript VM within a single thread (multithreading is achievable via the threaded code blocks that will be covered later). That is, if two scripts, a and b, are running in parallel and currently a normal code block from a is executing, no code block from b can be executed until the current code block finishes its execution.

There are various kinds of parallelism operators in SubScript. Their meaning is close to the meaning of the boolean logic operators. Here is a table that describes the common operators, their boolean logic analogues and meaning:

SubScript operator Boolean operator Meaning
 &&  AND  Wait until all the processes finish successfully, then terminate
 ||  OR  Wait until at least one of the processes finish successfully, then terminate

Here is a program that illustrates the behaviour of these operators:

import subscript.DSL._

object Main {
  def main(args: Array[String]): Unit = _execute([live])

    live = [
      println("&& Operator Test")
      println("\n|| Operator Test")

    andTest             = a && b
    orTest              = a || b
    exclusiveChoiceTest = a + b

    a = println("1") ; println("2") ; println("3")
    b = println("One") println("Two") println("Three")

In the main method, we run the script “live”. After the main method, script definitions follow. The def script..  syntax is a shorthand that specifies that everything that follows is a script definition. This way, no need to write def script before live, andTest, orTest and other scripts.

The live script contains various operands linked by a sequential operator (recall, that ;, space and the line break are all considered sequential operators by SubScript).
The first operand is println() – a method call. It is an ordinary Scala method call and it is outside the normal code or any other code block. The compiler wraps all the ordinary method calls in a “normal code” block implicitly. So that println() becomes equivalent to {println()}.
The second operand is andTest. There is a script called andTest (defined below), therefore it is a script call. In this place, andTest script will be executed. Only then the next operator in the sequence println("\n|| Operator Test”) will be executed.
The rest of the operands are similar to the ones just described.

The andTest script is defined as a && b. a and b are both scripts, so here we have and-parallel execution of scripts a and b.
The orTest script is defined as a || b – it defines the or-parallel execution of scripts a and b.

Scripts a and b both define a sequential execution of several normal code blocks (recall, that ordinary function calls are wrapped into normal code blocks) that output some text on the screen.

Let’s see the output of the program:

&& Operator Test

|| Operator Test

In case of &&, both scripts a and b finish their job completely. However, in case of ||, script b terminates prematurely (there’s no “Three” in the output). This is because a terminates prior to b (as you can see, “3” is the last line in the output) and, hence, the || operator terminates without waiting for b to terminate.

For better understanding of what’s going on here, it is strongly recommended to use the graphical debugger to analyze this code. You can read the lesson on the graphical debugger here.

Leave a Reply