4.3. def class: Define Site in Orc

The def class declaration provides the capability to implement sites within Orc itself. It is syntactically similar to the def declaration, but serves a different purpose.

The body of a def class consists of a sequence of declarations followed by a goal expression. Each def or def class declaration in this sequence is called a method.

Orc's def class provides encapsulation in a manner similar to classes in object-oriented languages. In addition to colocating methods and the shared resources on which they operate, an Orc class may also encapsulate some computation via the goal expression, colocating methods and resources with some orchestration which manages or monitors those resources.

4.3.1. Syntax

[23]DeclareDefclass::= def class Variable TypeParameters? Parameters ReturnType? Guard? = Expression  

4.3.2. Creating Instances

A def class declaration binds its identifier to a new site, which we call an Orc class. When this site is called, it creates and publishes an instance, a site which has all of the methods defined in the class.

In parallel, execution of the goal expression begins. Any value published by the goal expression is discarded. The class is a site, meaning that the execution of the goal expression cannot be killed by Orc; it continues independently of the rest of the program.

When an instance is created, each val declaration begins to execute, and each method is captured as a closure. This has two important implications:

  • Different instances will have different values for each val declaration, created by separate computations, and these bindings are captured by the method closures. In particular, this means that an instance could create some mutable resource, such as a mutable cell or a semaphore, which is then manipulated by the methods; different instances will have different underlying mutable resources. Instances are thus very similar to objects in object-oriented languages, and val declarations are analogous to private fields.

  • Recall that creation of a closure is strict in the closure's free variables. Thus, the creation of the instance may block on some free variable within the class (i.e. a variable bound by val), or a free variable in the scope of the class. In practice, this means that instance creation will block on every val declaration whose name is mentioned in any method. Furthermore, the goal expression might begin executing even before the instance is published.

4.3.3. Calling Methods

Each instance returned by a call to the class has a method for each def or def class declaration in the class body. A method is accessed using a dot access; the identifier name of the declaration is used as the key. When a method is called, the corresponding definition executes.

Each method behaves as a site, in particular a helpful site. Therefore,

  • A method call, unlike a function call, is strict in all of its arguments.

  • A method call publishes at most one value. If the method is a def, its first publication is used, and execution of the body continues, but subsequent publications are ignored.

  • If the method is a def, and execution of its body expression halts silently, then the method call also halts silently, exactly like a helpful site.

  • A method call cannot be killed by the pruning combinator. Once the method has been called, its execution continues independently of the rest of the Orc program.

The methods of an instance may be executed concurrently through concurrent invocations; there is no synchronization or serialization mechanism to prevent this. Different calls to the same method may even execute concurrently with each other. If any method uses a shared resource, care must be taken to ensure that different method invocations do not interfere with each other in unexpected ways.

4.3.4. Type

A def class declaration is typechecked in the same way as a def declaration, with one exception. The return type of a closure created by def class, rather than being the type of the body expression, is instead a record type {. m0 :: T0 ,, mn :: Tn .}, where each mi is the name of a method, and Ti is its type.

4.3.5. Examples

Matrix Definition

Orc's standard library supports only one dimensional arrays, and array indices always start at 0. We define a template for a 2-dimensional matrix whose row and column indices range over arbitrary intervals.

{- Create a matrix whose indices range from 
   (rowlo, collo) to (rowhi, colhi) inclusive,
   with a method to access its elements.
-}

def class Matrix((rowlo, rowhi), (collo, colhi)) =
 val mat = Array((rowhi - rowlo + 1) * (colhi - collo + 1))

 def access(i, j) = mat((i - rowlo) * (colhi - collo + 1) + j)

stop

{- Usage -}
val A = Matrix((-2, 0), (-1, 3)).access
A(-1, 2) := 5 >> A(-1, 2) := 3 >> A(-1, 2)?

{-
OUTPUT:
3
-}

Note: We have defined A as the "access" method of the defined matrix. This allows us to retrieve and update the matrix elements using traditional notation.

Create a Write-Once Site

We create a site, Cell, that defines a write-once variable. It supports two methods: read blocks until the variable has been written, and then it publishes its value; write(v) blocks forever if a value has already been written, otherwise it writes v as the value and publishes a signal.

We use two library sites, Semaphore (to ensure blocking of write if a value has been written) and Ref to store the value.

{- Create a mutable cell site -}

def class Cell() =
  val s = Semaphore(1)
  val r = Ref()

  def write(v) = s.acquire() >> r := v
  def read() = r?

  stop

val c = Cell()

c := 42 >> c?

{-
OUTPUT:
42
-}
Extend Functionality of an Existing Site

The Channel site implements an unbounded channel. We add a new method, length, that returns the number of items in the channel.

{- Extend the pre existing channel site with a length field -}

def class CustomChannel() =
  val ch = Channel()
  val chlen = Counter(0)

  def put(x) = chlen.inc() >> ch.put(x)
  def get() = ch.get() >x> chlen.dec() >> x
  def length() = chlen.value()

  stop

val cc = CustomChannel()

  signals(10) >> cc.put("item") >> stop
| signals(5) >> cc.get() >> stop
; cc.length()

{-
OUTPUT:
5
-}
Managing Concurrent Access

The methods of a class instance may be executed concurrently through concurrent invocations. Concurrent execution may cause interference, as in the example of the Newset example. Typically, semaphores are used to restrict access to methods and/or data. We rewrite Newset in which all accesses to shared data ne are protected using a semaphore.

{- Manage access to a set via a site -}

def class Newset(n) =
 val b = BoundedChannel(n)
 val (s , ne) = (Semaphore(1) , Ref(0))

 {- Add an element to the set if it is non-full.
   If the set is full, wait until the set becomes non-full.
   Return a signal on completion.
 -}
 def add(x) = b.put(x) >> s.acquire() >> ne := ne? + 1 >> s.release()

 {- Remove some element from the set if it is non-empty.
   If the set is empty, wait until the set becomes non-empty.
   Return the removed value.
 -}
 def remove() = b.get() >x> s.acquire() >> ne := ne? - 1 >>
   s.release() >> x

 {- Return the size, i.e., the number of elements currently in the set
 -}
 def size() = s.acquire() ne? >x> s.release() >> x

stop

{-
OUTPUT:
-}
Computing with the Goal Expression

All the goal expressions shown so far have been merely stop. In the following example, the goal expression of the class initiates an computation in which a number is printed every second; all the publications of the goal expression are ignored.

{- Perform a print action with the goal expression 
   of a class: counting down from n to 0.
-}

def class countdown(n) =

 def downfrom(i) if (i >= 0) = i | Rwait(1000) >> downfrom(i-1)

 {- Goal -} downfrom(n) >i> Println(i)

val _ = countdown(5)
{- Goal of the whole program -}  stop

{-
OUTPUT:
5
4
3
2
1
0
-}
Stopwatch
{- 
  An instance of Stopwatch has the following operations:
     
    start():  
      Start a paused stopwatch and publish a signal.
      If the stopwatch is already running, halt instead.
    finish(): 
      Pause a running stopwatch and publish the time
      that has passed since it was last started.
      If the stopwatch is already paused, halt instead.
-} 

def class Stopwatch() =
  val now = Rclock().time 
  val checkpoint = Ref(now())
  val (startlock, finishlock) = (Semaphore(0), Semaphore(0))
  def start() = 
    startlock.acquireD() >>
      now() >starttime> 
      checkpoint := starttime >>
    finishlock.release()
  def finish() =
    finishlock.acquireD() >>
      checkpoint? >starttime>
      now() >endtime>
      checkpoint := endtime >>
    startlock.release() >>
    endtime - starttime
  
  startlock.release()    
  
  
val watch = Stopwatch()

watch.start() >> Rwait(400) >> watch.finish() >i>
Rwait(200) >>
watch.start() >> Rwait(100) >> watch.finish() >j>
(i+j >= 500) && (i+j <= 525)

{-
OUTPUT:
true
-}

4.3.6. Related Links

Related Tutorial Sections