Menu

Co-op III

Steven te Brinke
Attachments
FliCall.png (9093 bytes)

Co-op/III is still in development. Currently, a partial implementation is available. This implementation does not offer the essential constructs of Co-op.

Developing

The steps to get the development environment up and running should be as follows:

  • Locate the projects in svn at http://svn.code.sf.net/p/co-op/code/coopIII/trunk/... (to have commit access, use https and login with your SourceForge account)
  • There are 5 projects starting with net.sf... check out all except ...systemtests.
  • You will have compile errors because the generated resources are missing.
  • Go to the ...coopiii project in the Java Package Explorer. Open the package src/net.sf....coopiii. Rightclick on CoopIII.xtext. Choose Run As -> Generate XText Artifacts. (You might need to ensure enough memory is available by setting -Xms40m -Xmx384m.)
  • Wait until this has completed. (You might have to refresh your workspace. Potentially you also have to clean the workspace/trigger a full build. If you still have errors, try to repeat refresh and clean.)
  • Right-click the ...tests project and say Run As -> JUnit Test. You should see all tests pass.
  • From the Run menu choose "Run Configurations ...". Under Eclipse Application you will see "Co-op IDE". Select this and run it.
  • In the runtime workbench you can check out the net.sf...systemtests project from the SVN. That contains the Co-op files which should all compile. You should again be able to Run As -> JUnit Test and see all tests pass. (You might need to edit a single Co-op source file to trigger compilation, especially if the src-gen folder is empty.)

Design

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.

JVM

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.

Code generation

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.)

Method calls

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.

Field access

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).

  1. If we define all fields F in CoopObject and define a subset S of these in a subclass, then this subclass will have all fields F + S, where for all fields in S two instances with the same name exist (the one from S and the one from F). This makes debugging quite difficult (how to distinguish between multiple fields with the same name) and can cause unexpected behavior (depending on the static type of a variable, one of the fields is used). Therefore, this would not be a good solution.
  2. If we define all fields in CoopObject and do not define a subset of these in the subclasses, we can no longer identify which class contains which fields, since all fields are always available. This can result in unexpected behavior at runtime and therefore is not a desired solution either.
  3. Another possibility is to generate getters and setters and use these. Since field access in Co-op is modeled the same as method calls, changing field access to method calls does not have any adverse effects. Since the getter and setter methods are left unimplemented in CoopObject (that is, an implementation that always throws a runtime exception is provided), we do not have to define the fields in CoopObject. Fields are defined in the classes for which the Co-op code actually defines them, which ensures that we always know which class contains which fields.

Solution 3 is implemented in the current prototype.

Methods of java.lang.Object

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:

  • If a Co-op class defines a method called hashCode(), it is compile to hashCode$(), so it does not interfere with the method defined in Object.
  • When a Co-op class calls hashCode(), it actually calls hashCode$(), which is the version defined by Co-op. Only when the target is a Java class instead of a Co-op one, the Java integration will ensure that the implementation of java.lang.Object is called and the result is wrapped to become a CoopObject.
  • If Java code calls hashCode() on any Co-op object, any implementation of hashCode() in Co-op is not visible (since it was compiled to hashCode$()). Changes to this behavior could be made when [Co-op objects in the Java world] is developed further.

Naming convention of generated methods

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'').

IDE

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.

CoopUnit a.k.a. JUnit

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.

Bindings

TBD

Annotations

We would like to represent annotations in multiple situations.

  1. At definition time on methods, fields, etc.
    1. Java annotations can be used, but as parameters they require literals, whereas we would like the freedom to use any Co-op type.
    2. Only Java annotations can be stored here, so a custom representation is only possible if it can be mapped onto Java annotations.
  2. At call side.
    1. Java annotations can be used, but it requires the instantiation of proxies implementing these annotations and has the same limitations as 1.1.
    2. We can use our own implementation, not conforming to Java annotations at all, which gives us full freedom here, but these do not work at definition time as described at 1.

Possible solutions are:

  1. Using 1.1 plus 2.1 and encoding all Co-op objects as int references into Java annotations would work. However, this still has limitations:
    1. Co-op objects used by annotations must be stored in some central store.
    2. Accessing the parameter values of annotations requires some indirection, since these are in some central store.
    3. Using Java annotations inside Co-op is not possible since the parameters values should not be references to Co-op objects here.
  2. Using 2.1 and put all annotations in a custom store (so they are not present at definition time as in Java).
    1. As in the previous solution, this also requires a special store.
    2. Since we cannot reuse Java's reflective capabilities for reflection, the store itself will provide all reflective capabilities. This will avoid the need for any indirection when accessing annotation parameters.
    3. Java annotations cannot be fully used inside Co-op, as in the previous solution. (We could allow using them inside Co-op, having them only visible to Co-op code and not to the JVM itself. But we might also restrict this as it changes the working of the annotations.)

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:

  • Leave it as is and accept runtime errors.
  • Try to limit the number of runtime errors, e.g. by only calculating the default value when needed. This cannot avoid all errors.
  • Do not allow the use of annotations on default values. Right now, default values are very expressive, they can be any expression. Why would we need such expressiveness? Wouldn't it be better to limit it and avoid runtime errors caused by default values.
  • Try to detect cyclic dependencies and disallow these, but keep allowing all other situations. Probably, this cannot be sound and complete due to undecidability, so a compromise must be made.

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

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:

  • When typos are made -> for methods, typos are not discovered at compile time either, so not a real good reason to define them
  • When bindings unexpectedly leave variables undefined -> if implicit parameters are defined, it will fail during dispatch, otherwise it will fail during execution (e.g. calling a non-static method in a static way will then cause failure when 'this' is referenced for the first time¹, whereas now the dispatch will fail already)

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

Continuations

TBD

Java integration

The integration of Co-op with Java requires the following situations to be handled:

  1. Co-op objects should be able to live in the Java world.
  2. Java objects should be able to live in the Co-op world.
  3. Co-op code should be able to call methods of Java objects.

Co-op objects in the Java world

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:

  1. The object passed is a Java object of type T living in the Co-op world, so it can easily be passed to the Java world.
  2. The object passed is a Co-op object conforming to T. For now, we cannot check the conformance so we do not allow this.

Java objects in the Co-op world

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.

Calling methods on Java objects

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:

Image showing Foreign Language Integration

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.