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.
|
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.
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.
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.
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.
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 -}
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 -}
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: -}
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 -}
{- 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 -}
Related Reference Topics
Related Tutorial Sections