Co-op/III is still in development. Currently, a partial implementation is available. This implementation does not offer the essential constructs of Co-op.
The steps to get the development environment up and running should be as follows:
The basic idea of Co-op/III is to provide a prototype based on Co-op/II including some improvements. This section will describe the (ongoing) design decisions made for Co-op/III.
We would like to provide a common platform to deploy Co-op applications easily. Further, we need a way to implement bindings, for which ALIA seems to offer a nice framework. Also, we hope to improve performance over version II. Therefore we decided that Co-op/III runs on the JVM.
For the ease of code generation and debugging, we have decided to compile to Java source code. Compiling to Java byte code would give us more expressiveness, but would also make the code generation more difficult. Since most expressions can be mapped to Java source code easily, we decided that using this would be the best solution as long as it does not limit our possibilities.
Since Java is typed statically, the generated code should be correct under static type checking. To ensure this, we generate a class CoopObject, which is the super class of any object defined in Co-op. That is, the relation between the class CoopObject and any class defined in Co-op is the same as the relation between the class Object and any class defined in Java. CoopObject implements every method that is used somewhere in the Co-op code, in order to type check correctly. These methods all throw a runtime exception, since only when an actual implementation is provided in Co-op, a call to them can succeed. (Currently, the generated methods contain some boilerplate code to ensure no runtime exception is thrown if the method was actually implemented in Co-op. This should be transparent to the Co-op programmer as long as he writes correct programs.)
In Co-op, method calls can be annotated, which is not possible in Java. Therefore, we have to keep our own stack storing these annotations. This is done by creating a helper method for every method call inside Co-op. This helper method pushes all annotations on the stack, performs the actual call and pops the annotations again. This way, the annotations belonging to the current call are always available.
In Co-op, field accesses are message sends just like method calls. However, we cannot define all fields inside CoopObject like we do with methods, and override them in subclasses, since field lookup cannot be dynamic (it is always done at compile time).
Solution 3 is implemented in the current prototype.
Co-op does not contain any inheritance, therefore an object should not have a basic set of methods. However, we cannot avoid classes to inherit from java.lang.Object, which gives them a few methods (e.g. hashCode() and toString()). Since their signatures do not match the signatures of Co-op methods, they cannot be used or overridden from Co-op. Therefore, we have decided to hide these methods from Co-op in the following way:
Methods which are defined in Co-op, will in general remain their defined name when compiled to Java. Exceptions are:
Name | Meaning |
---|---|
«method name»$ | Method names that are Java keywords (abstract, new, switch, assert, default, goto, synchronized, boolean, private, double, implements, protected, byte, public, throws, case, instanceof, transient, catch, extends, int, short, try, char, final, interface, static, void, finally, long, strictfp, volatile, const, float, native, super) cannot be used inside Java. In this case, a $ is postfixed to the method. A few methods that are defined in java.lang.Object (toString, hashCode, equals) are treated as if these names are Java keywords, as described in [Methods of java.lang.Object]. |
$«method name» | Static methods are prefixed with a $ to avoid hiding instance methods (which is not allowed). |
Helper methods have the following names:
Name | Meaning |
---|---|
get$«field name» | Getter for field access. |
set$«field name» | Setter for field access. |
read_«field name»$«n» | Field read helper method. |
write_«field name»$«n» | Field write helper method. |
new«annotation type»$«n» | Annotation creation helper method. |
«method name»$«n» | Any method call. |
Special test methods are generated allowing JUnit to invoke Co-op code (these methods are not used by Co-op itself):
Name | Meaning |
---|---|
«method name»$test | Any method annotated with @Test. |
For fields, we use a similar naming scheme:
Name | Meaning |
---|---|
«field name»$ | Field names that are Java keywords. |
«field name»$default | Default value of an annotation field (these fields are ''final static''). |
To ease writing of examples and test cases, an IDE offers clear benefits. Using tools like Xtext, adding IDE integration to a parser is not very difficult. So Co-op/III provides an Eclipse IDE.
Unit testing is important for any piece of software, including ours. Therefore, JUnit integration is added to Co-op, so unit tests can be written easily.
TBD
We would like to represent annotations in multiple situations.
Possible solutions are:
We have chosen solution 2, since it gives the largest flexibility. Integration with Java annotations could be achieved in future by creating a way to map Co-op annotations to their Java counterparts, if we see this as a necessity for using important Java libraries. Full integration does not seem to be feasible, since we do not want to compromise the flexibility of the Co-op model.
We have a working example of adding annotations, but reflection has not been designed and tested yet.
It is possible to specify annotations on the default value of an annotation property, but it is easy to create cyclic references making the annotation unusable, so it might be better to avoid using annotations on default values. For example, when we use annotation @A on the default value of A.value, then in order to instantiate @A, the default value must be calculated (this is currently done in any case, even if the instance that is being created specifies its own value), which requires @A to be instantiated. This cyclic dependency will result in a runtime error. There are several possible things we could do:
TBD: Most annotations are stored to be used by reflection (annotations on parameters are missing), but this should be designed more carefully to allow proper reflection
Implicit parameters are parameters that are passed to a method implicitly. In Co-op/II, they must be defined before they can be used. But should we actually require defining them?
Not defining them can cause access to undefined variables at runtime:
Can be confused with field access, e.g. someField = 5 does not assign to a field, but to an implicit parameter. To assign to the field, use this.someField = 5.
If the programmer forgets to define a local variable, this might work in the presence of bindings that define it as an implicit parameter, but fails otherwise.
¹ Or we could enforce the existence of 'this' at the begin of the method, making it fail as early as possible. For example by adding the following code for every implicit parameter used:
CoopObject this = CoopRuntime.getImplicitParameterValue("this");
TBD: Decide what we'll use in Co-op/III
TBD
The integration of Co-op with Java requires the following situations to be handled:
Since Co-op objects all inherit from the Java class Object, Co-op objects can easily be passed where Java expects an Object. Whenever Java expects another type T there are two possibilities:
To allow Java objects to live in the Co-op world, we can wrap them inside a wrapper class. Since Co-op is typed dynamically, a generic wrapper class JavaObject can be defined. Then, whenever a Java object enters the Co-op world, it can be wrapped inside a JavaObject and when it wants to enter the Java world again, the only thing to do is unwrap it before letting it in.
Since Java resolves methods at compile time using their static types, Co-op cannot provide the same behavior. Co-op will provide dynamic dispatch for these objects. We decided to use asymmetric dynamic dispatch for any call made from Co-op to Java. We expect that this will induce the behavior expected by a Java programmer in most cases. We also plan to provide some annotations to allow static method selection.
The current prototype has an experimental implementation of Java integration. This implementation causes some additional indirection that can be seen at binding level. Instead of the Java type, we always see the type JavaObject from the Co-op world, as shown in the following figure:
Currently the left message is generated and then JavaObject calls the actual implementation in String. It would be desired to create the right message, but that requires a special binding to handle this message, which is not possible with the current state of the implementation.
If we manage to remove this additional indirection, we might be able to remove all Co-op primitives and replace them with Java classes. Since the Co-op primitives provide more functionality than their Java counterparts, we'll need to write a binding that adds this functionality to the Java counterparts.