Java classes can be imported into Orc as sites using the
class declaration.
Imported classes must be in the classpath of the JVM running the Orc
interpreter. The following sections describe in detail how such imported
classes behave in Orc programs.
x.member, where x evaluates to a Java class or object, is evaluated as follows:
x has one or more methods named member,
a "method handle" site is returned which may be called like any other Orc site.
When a method handle is actually called with arguments, the appropriate Java
method is selected and called depending on the number and type of arguments, as
described in Method Resolution
below.x has a field named member,
the object's field is returned, encapsulated in a
Ref object. The
Ref object has read and write methods
which are used to get and set the value of the field.Note that no distinction is made between static and non-static members; it is an error to reference a non-static member through a class, but this does not change how members are resolved. Note also that if a field shares a name with one or more methods, there is no way to access the field directly.
The following (rather useless) example illustrates how the dot operator can be used to access both static and non-static methods and fields:
{- bind Integer to a Java class -} class Integer = java.lang.Integer {- call a static method -} val i = Integer.decode("5") {- read a field -} val m = Integer.MIN_VALUE.read() {- write a field -} Integer.MIN_VALUE.write(5) >> {- call a non-static method -} i.toString()
When x evaluates to a Java object (but not a Java class), the syntax
x(...) is equivalent to x.apply(...).
When x evaluates to a Java class, the syntax x(...)
calls the class's constructor. In case of overloaded constructors, the
appropriate constructor is chosen based on the number and types of arguments as
described in Method Resolution.
If C is bound to a Java class, it can be used as a pattern.
A pattern C(x) matches any Java object of that class or
any of its subclasses. The variable x is simply bound
to the object again; thus the matcher is just a partial identity function.
When a method handle is called, the actual Java method called is chosen based on the runtime types of the arguments, as follows:
BigDecimal,
the argument is implicitly narrowed to the formal parameter type.BigInteger,
the argument is implicitly narrowed to the formal parameter type.
The reason for the unusual implicit narrowing of BigDecimal and
BigInteger is that Orc numeric literals have these types, and it
would be awkward to have to perform an explicit conversion every time such a
value is passed to a Java method expecting a primitive.
Currently we do not implement specificity rules for choosing the best matching method; the first matching method (according to some unspecified order) is chosen. Note also that we do not support varargs methods explicitly, but instead varargs may be passed as an array of the appropriate type.
Orc values are implemented by Java objects, so in general any Orc value may be passed to a site implemented in Java. Standard Orc values have the following Java types:
java.lang.Stringjava.lang.Booleanjava.lang.Numberorc.runtime.values.TupleValueorc.runtime.values.ListValueorc.runtime.values.ClosureCurrently it is not possible to call Orc functions from Java code.
orc.runtime.values.SiteCurrently it is not possible to directly call Orc sites from Java code. However if you are implementing a site yourself, you may provide methods which can be called from Java code to invoke the behavior of the site.
Java objects may be used directly as values anywhere in an Orc program. Primitive Java values cannot be used directly in an Orc program, but are automatically boxed (and unboxed) as necessary.
When both arguments of an arithmetic or comparison operator are Java numeric types, the arguments are implicitly coerced to the widest of the two argument types. "Widest" is defined by the following relation, where ">" means "is wider than": BigDecimal > Double > Float > BigInteger > Long > Integer > Short > Byte
In order to support massive concurrency efficiently in Java, Orc uses cooperative threading. Orc programs are broken into discrete steps which are executed by a fixed-size thread pool. This approach works for Orc expressions, but the internals of an Orc site written in Java cannot be easily broken down. So Orc has two choices when it needs to call a local Java site:
Fortunately, in practice most Orc site calls take a short, deterministic amount
of time to complete. Of those that don't, it's usually only because they are
blocked waiting for some external event, like another site call. In this case,
instead of blocking the site can return control to the Orc engine, asking to
be run again when the external event occurs. In typical Java applications this
kind of non-blocking behavior is implemented using callbacks (or in its most
general form, continuation passing). Unfortunately this creates convoluted and
verbose code: what if you need to block in the middle of a for loop, for example?
Kilim resolves this issue by allowing programmers to write code in a natural blocking style and then rewriting the bytecode to perform a form of CPS conversion, so that instead of blocking the code actually returns control to a scheduler. Orc supports Kilim, allowing site authors to write sites with internal concurrency and blocking behavior which don't use Java threads and cooperate with the Orc engine.
For a full introduction to Kilim, see A Thread of One's Own, by Sriram Srinivasan. In the following, we will cover just enough to get you started writing Orc sites using Kilim.
Kilim introduces three key primitives:
Pausable
This checked exception marks methods which may block. You should never catch
this exception, and you should always declare it even if you have already
declared throws Exception or throws Throwable. In all other respects it follows the
normal rules for checked exceptions: an override can only throw it if the
superclass method throws it, and you must throw it if you call any method which
throws it.
Originally Kilim used annotations to mark pausable methods. It turned out that javac would sometimes manipulate code in ways which violate the invariants regarding use of the annotation. Annotations also have the disadvantage that the invariants on their use are not checked automatically by tools like Eclipse.
Mailbox
A multiple-producer, single-consumer queue used to communicate
between tasks. The most important methods are put(Object), which places an item in the
mailbox, and get(), which blocks until the mailbox
is non-empty and then returns the first item.
Task
Thread. Every Pausable
method must be run within a task. To create and run a task:
new Task() { public void execute() throws Pausable { // do some stuff } }.start();
Together, these primitives allow you to write highly-concurrent Java programs in a familiar threaded style. Under the hood, Kilim can schedule any number of concurrent tasks on a fixed number of physical threads, providing better scalability and performance than possible with Java threads.
When writing Orc sites you rarely need to use tasks explicitly because every Orc
site call is implicitly run inside a Kilim task if necessary. Therefore all you need to do is mark
Pausable methods. Here's a short example of a
buffer site written using Kilim Mailboxes:
public class KilimBuffer<V> { private LinkedList<Mailbox<V>> waiters = new LinkedList<Mailbox<V>>(); private LinkedList<V> buffer = new LinkedList<V>(); public synchronized void put(V o) { Mailbox<V> waiter = waiters.poll(); if (waiter != null) waiter.putnb(o); else buffer.add(o); } public synchronized V get() throws Pausable { V out = buffer.poll(); if (out != null) return out; else { Mailbox<V> waiter = new Mailbox<V>(); waiters.add(waiter); return waiter.get(); } } }
Classes using Kilim can be imported like other Java classes using the Orc
class declaration. For example, we can use the
KilimBuffer class defined above:
class Buffer = orc.lib.state.KilimBuffer val b = Buffer() Rtimer(1000) >> b.put("1 second later") >> null | b.get()
new Foo(bar.pausable());
The workaround is to use a temporary variable:
Object tmp = bar.pausable();
new Foo(tmp);
Sometimes blocking is unavoidable, for example if a site must perform blocking
IO. For such cases, Orc provides a utility method orc.runtime.Kilim#runThreaded(Callable) which farms work
out to a thread pool. The advantage of doing this over spawning your own
thread is that there is no chance of using too many threads; if no thread is
available, the method pauses until one becomes available. The disadvantage is
that if you have too many Java methods which must communicate with each other
concurrently and can't use Mailboxes, there won't be enough threads for them
all and execution will deadlock. This situation is sufficiently rare that
runThreaded is usually the correct approach.
Code which uses Kilim must be processed with the Kilim "weaver" after compiling and before running. The weaver is bundled with the Orc source code, or may be downloaded separately from the Kilim project site.
To process compiled classes, run kilim.tools.Weaver.
If your .class files are in the
build/ directory, you would run the weaver like this:
java -cp kilim.jar:build/ kilim.tools.Weaver -d build/ build/
The Orc source distribution includes an ant kilim
task (in build.xml) which weaves compiled classes in the
build directory. If you are compiling Orc in Eclipse, an
Eclipse "Builder" is included which does this automatically whenever any source
files change.