This page contains the information about the ocamlwrap tool that ships with the preview binary distribution of OCaml-Java 2.0.

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

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 ... filen.cmi
where possible options are:
command-line switchdefault valuemeaning
-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 initializatio
-library-init <li>
with li among explit, static
explicitlibrary initialization mode
-library-package <string>nonelibrary package
-no-warningswhether to disable warnings
-package <string>""package of generated classes
-string-mapping <sm>
with sm among java-string, ocamlstring, byte-array
java-stringmapping for OCaml string type
-verbosewhether to enable verbose mode

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.

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.

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

OCaml typeJava type (boxed)Java type (unboxed)implemented interfaces
intOCamlIntlongOCamlNumber
charOCamlCharint-
stringOCamlStringaccording to -string-mappingCharSequence
floatOCamlFloatdoubleOCamlNumber
boolOCamlBoolboolean-
unitOCamlUnit--
int32OCamlInt32intOCamlNumber
int64OCamlInt64longOCamlNumber
nativeintOCamlNativeIntlongOCamlNumber
'a optionOCamlOption<T>--
'a lazy_tOCamlLazy<T>--
'a arrayOCamlArray<T>-Iterable<T>
'a listOCamlList<T>-Iterable<T>
'a refOCamlRef<T>--
in_channelOCamlInChannel--
out_channelOCamlOutChannel--

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.

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 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("...");
    }
});

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 exceptionJava exception
Assert_failureOCamlAssertFailureException
Division_by_zeroOCamlDivisionByZeroException
End_of_fileOCamlEndOfFileException
FailureOCamlFailureException
Invalid_argumentOCamlInvalidArgumentException
Match_failureOCamlMatchFailureException
Not_foundOCamlNotFoundException
Out_of_memoryOCamlOutOfMemoryException
Stack_overflowOCamlStackOverflowException
Sys_blocked_ioOCamlSysBlockedIOException
Sys_errorOCamlSysErrorException
Undefined_recursive_moduleOCamlUndefinedRecursiveModuleException

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

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

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.

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.

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.

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.

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