OCaml-Java: accessing OCaml code from the Java language


This page contains the information about the ocamlwrap tool that ships with the alpha version of OCaml-Java 2.0.

Warning! ocamlwrap is a new tool, still at the experimental stage.

Warning! ocamlwrap does not support the typer extension allowing to access Java elements.

Summary

Since version 2.0-early-access6, the distribution features the ocamlwrap tool in order to produce Java class definitions allowing to call ocamljava-compiled code from the Java language.

The wrapper is invoked through a command line such as the following one:

ocamlwrap <options> file1.cmi[@pack1] ... filen.cmi[@packn]

where the optional @packi components can be used to specify the package for the generated class of the associated cmi file.

The available options are:

command-line switch default value meaning
-class-name-prefix <string> "" prefix for names of generated classes
-class-name-suffix <string> "Wrapper" suffix for names of generated classes
-library-args <string> new String[0] arguments passed for library initialization
-library-init <li> explicit library initialization mode
-library-package <string> none library package
-no-warnings whether to disable warnings
-package <string> "" package of generated classes
-string-mapping <sm> java-string mapping for OCaml string type
-verbose whether to enable verbose mode

where:

  • li is among explit, static;
  • sm is among java-string, ocamlstring, byte-array

The wrapper generates plain Java source files that can be read or passed to the javadoc tool in order to inspect the API. As the tool name suggests, ocamlwrap generates wrapers meaning that values are actually shared between the OCaml and Java runtimes. Side effects occuring on one side are thus observed on the other side.

Example

Suppose a toy library made of a single Lib module. The library can be compiled (as a program) using the following commands:

ocamljava -java-package wraptest -c lib.mli
ocamljava -java-package wraptest -c lib.ml
ocamljava -java-package wraptest -o lib.jar lib.cmj

and Java wrappers can be generated by executing:

ocamlwrap lib.cmi

that will result in a new LibWrapper.java file.

Then, it is possible to use the functions exposed in the lib.mli file from Java through the LibWrapper.java file, that can be compiled through:

javac -cp lib.jar LibWrapper.java

For example, if lib.mli contains the following declarations:

type connection = {
  login : string;
  mutable timestamp : int64;
}
val connections_to : string -> connection list

then, the Java code can look like:

public static void main(String[] args) throws Exception {
    wraptest.ocamljavaMain.mainWithReturn(args);

    ...

    for (LibWrapper.connection c : LibWrapper.connections_to("...")) {
        log.printf("user %s since %d\n", c.getLogin(), c.getTimestamp());
    }
}

where the first line of the main method is mandatory, and responsible for calling the initialization code of the OCaml library. The following sections show how the various elements of a mli file are mapped to their Java equivalents.

Core types

The core (or predefined) types of the OCaml language are mapped as follows:

OCaml type Java type (boxed) Java type (unboxed) implemented interfaces
int OCamlInt long OCamlNumber
char OCamlChar int -
string OCamlString (1) CharSequence
float OCamlFloat double OCamlNumber
bool OCamlBool boolean -
unit OCamlUnit - -
int32 OCamlInt32 int OCamlNumber
int64 OCamlInt64 long OCamlNumber
nativeint OCamlNativeInt long OCamlNumber
'a option OCamlOption<T> - -
'a lazy_t OCamlLazy<T> - -
'a array OCamlArray<T> - Iterable<T>
'a list OCamlList<T> - Iterable<T>
'a ref OCamlRef<T> - -
in_channel OCamlInChannel - -
out_channel OCamlOutChannel - -
  1. depends on -string-mapping

The javadoc-generated documentation for all predefined classes is available in the doc directory of the distribution.

Warning! All operations on OCaml channels are buffered. It is thus necessary to flush channels at appropriate points when the same channel/stream is used on both OCaml and Java sides.

Tuples

All OCaml tuples are mapped to Java classes with names OCamlTupleN, where N is the tuple size. This means, for example, that the OCaml type int * float is mapped to the Java type OCamlTuple2<OCamlInt, OCamlFloat>. The current implementation only supports tuples up to size 8.

It is important to notice that the class OCamlTupleN does not inherit from any of the class OCamlTupleK where K < N. This is consistent with the fact that it is illegal in OCaml to pass a triple where a couple is actually waited.

Functions

Functions are translated in two different ways:

  • as static methods if they are top-level functions of the module passed to the ocamlwrap tool;
  • as instances of classes with names OCamlFunctionN<T1, ..., Tn, TR> (where TR is the return type, while the Ti are the parameter types).

In the first case, the function is invoked by a call such as LibWrapper.f(p1, ..., pn), while in the second case the function should be invoked by a call such as f.execute(p1, ..., pn). OCamlFunctionN classes are akin to OCamlTupleN classes (except that N is only up to 5), but are abstract classes. A concrete implementation is typically retrieved by:

  • getting the result of a function;
  • calling LibWrapper.f$closure() to get the instance associated with top-level function LibWrapper.f;
  • implementing it in the Java language by extending an OCamlFunctionN class (the only method to implement being execute(...)).

For example, if the OCaml code defines a function with the following signature:

val call : (int -> int -> string) -> string

it is possible to invoke it with an instance of OCamlFunction2<OCamlInt,OCamlInt,OCamlString> through:

OCamlString s = LibWrapper.call(new OCamlFunction2<OCamlInt, OCamlInt, OCamlString>() {
    public OCamlString execute(OCamlInt p0, OCamlInt p1) {
       ...
       return OCamlString.create("...");
    }
});

Exceptions

The ocamlwrap tool takes care of converting exceptions between their OCaml and Java representations where needed. As a consequence, the developer only needs to know the mappings between OCaml and Java exceptions (see table below); catching and raising exceptions is done by manipulating the Java exceptions as usual.

OCaml exception Java exception
Assert_failure OCamlAssertFailureException
Division_by_zero OCamlDivisionByZeroException
End_of_file OCamlEndOfFileException
Failure OCamlFailureException
Invalid_argument OCamlInvalidArgumentException
Match_failure OCamlMatchFailureException
Not_found OCamlNotFoundException
Out_of_memory OCamlOutOfMemoryException
Stack_overflow OCamlStackOverflowException
Sys_blocked_io OCamlSysBlockedIOException
Sys_error OCamlSysErrorException
Undefined_recursive_module OCamlUndefinedRecursiveModuleException

Additionally, the OCamlExn class is used as the mapping for the OCaml exn type.

Sum types

An OCaml sum type is mapped to a Java class providing factory methods to create the various cases, and accessors for each nested value of each case. For example, the following type:

type sum =
  | Int of int
  | String of string
  | Empty
  | Int_and_string of int * string

is mapped to the class:

class sum extends OCamlValue {
  public long getInt0();
  public String getString0();
  public long getInt_and_string0();
  public String getInt_and_string1();
  public TAG tag();
  public static sum createInt(long);
  public static sum createString(String);
  public static sum createEmpty();
  public static sum createInt_and_string(long, String);
  public static enum TAG { Int, String, Empty, Int_and_string }
  public <T> T visit(Visitor<T> visitor);
}

where the inner-class TAG is used to encode the various cases as a Java enum.

The visit method allows to inspect the value through the visitor design pattern, with the Visitor interface generated for the sum type; in out example:

interface Visitor<T> {
  T visitInt(long);
  T visitString(String);
  T visitEmpty();
  T visitInt_and_string(long, String);
}

The use of the visitor design pattern should be prefered to the use of the tag and accessor methods, as it provides guarantees close to the ones provided by OCaml pattern matching:

  • exhaustivity;
  • correct access to nested values.

Polymorphic variants

The current version of ocamlwrap requires that polymorphic variants:

  • are closed;
  • have their type declared.

This means that one should declare:

type t = [ `A | `B of int ]
val f : int -> t

rather than:

val f : int -> [ `A | `B of int ]

The combination of both aforementioned restrictions allows to treat polymorphic variants as bare sum types.

Records

The mapping of records is straightforward, following the convention of JavaBeans. This means that for each field named xyz, an accessor named getXyz() is generated allowing to retrieve the field value. If the field is mutable, another accesor named setXyz(...) is also generated allowing to modify the field value.

For example, if the following record is defined in OCaml:

type connection = {
  login : string;
  mutable timestamp : int64;
}

it will result in the following Java class:

class connection extends OCamlValue {
  public String getLogin();
  public long getTimestamp();
  public void setTimestamp(long);
  public static connection create(String, long);
}

where the create(...) method is a factory method allowing to create new instances.

Polymorphism

The ocamlwrap tool supports polymorphism both in type declarations, and in function declarations. However, the ocamlwrap tool is not always able to determine how to wrap a value. When this occurs, the generated Java methods will ask for additional parameters with types Wrapper<T>. An instance of Wrapper<T> is just an object that knows how to wrap a value into type T.

Every Java class mapping an OCaml type provides a wrapper method allowing to retrieve a wrapper. If the OCaml type has no type parameter, the wrapper method takes no parameter (e.g. OCamlInt.wrapper()). Otherwise, the wrapper method should be passed a wrapper for each type parameter, leading for example to:

  • OCamlList.wrapper(OCamlString.wrapper()) to get a wrapper for type string list;
  • OCamlTuple2.wrapper(OCamlInt32.wrapper(), OCamlInt64.wrapper()) to get a wrapper for type int32 * int64.

Class types

The current version of ocamlwrap provides only partial support for OCaml objects: it is only possible to wrap class types (meaning that classes and immediate objects are not supported).

OCaml class types are mapped to Java abstract classes. For example, the following:

class type simple = object
  method first_method : unit
  method second_method : int -> float
  method third_method : int32
end

is mapped to:

abstract class simple extends OCamlValue {
  public abstract void first_method() ;
  public abstract double second_method(long) ;
  public abstract int third_method() ;
}

There are essentially two ways of getting an instance of such a class:

  • as the result of an OCaml function;
  • as the instance of a Java class inheriting from the abstract one.

In the second case, a concrete Java class is defined as usual:

class MySimple extends LibWrapper.simple {
  public void first_method() { System.out.println("in first method"); }
  public double second_method(long x) { return ((double) x) / 10.0; }
  public int third_method() { return 1; }
}

Warning! Currently, class types can neither inherit from other class types, nor contain values.

Functors

The current version of ocamlwrap provides only lightweight support for OCaml functors, and module types can only contain functions and abstract type definitions. The mapping of module types is very similar to the one of class types, except that type declarations appear as generics. For example:

module type MT1 = sig
  type t
  val cost : t -> int
end

is mapped to:

abstract class MT1<t extends OCamlValue> extends OCamlValue {
  public abstract long cost(t) ;
}

The declaration of functors requires that all involved module types have been previously declared, thus leading to definitions like:

module type P = sig ... end
module type R = sig ... end
module M (X : P) : R with type ...

rather than:

module M (X : sig ... end) : sig ... end

Functors are mapped to static methods, just like ordinary top-level functions.

It is possible to mix OCaml and Java by instantiating a functor whose parameters have been implemented in Java. For example, considering the following OCaml declarations:

module type MT1 = sig
  type t
  val cost : t -> int
end

module type MT2 = sig
  type element
  type container
  val make : unit -> container
  val add : container -> element -> unit
  val total : container -> int
end

module Make (P : MT1) : MT2 with type element = P.t

It is possible to develop an implementation for MT1 in Java:

class MT1Impl extends LibWrapper.MT1<OCamlString> {
  public MT1Impl() {
    super(OCamlString.WRAPPER);
  }
  public long cost(OCamlString s) {
    long res = 0L;
    int len = s.length();
    for (int i = 0; i < len; i++) {
      char ch = s.charAt(i);
      if (ch == 'e') res++;
    }
    return res;
  }
}

And to pass it to the OCaml functor:

MT1Impl mt1 = new MT1Impl();
LibWrapper.MT2<OCamlString,OCamlValue> mt2 = LibWrapper.Make(mt1);
OCamlValue container = mt2.make();
mt2.add(container, OCamlString.create("abc"));
mt2.add(container, OCamlString.create("def"));
mt2.add(container, OCamlString.create("ghi"));
System.out.printf("total=%d\n", mt2.total(container));

Warning! Contrary to other elements, the use of functors heavily relies on developer discipline. Indeed, in OCaml, applications of a functor to different parameters lead to different modules where embedded types are different from one application to the other. It is not possible to reflect these differences in the Java wrappers, thus leading to the possibility to mix the values generated by different functor applications, hence in turn breaking type safety.