This page contains the information about the ocamlwrap tool that ships with the alpha version of OCaml-Java 2.0.
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 initializatio |
-library-init <li> with li among explit , static | 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> with sm among java-string , ocamlstring , byte-array | java-string | mapping for OCaml string type |
-verbose | whether 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.cmjand Java wrappers can be generated by executing:
ocamlwrap lib.cmithat 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 listthen, 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 type | Java type (boxed) | Java type (unboxed) | implemented interfaces |
---|---|---|---|
int | OCamlInt | long | OCamlNumber |
char | OCamlChar | int | - |
string | OCamlString | according to -string-mapping | 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 | - | - |
The javadoc
-generated documentation for all predefined classes is available in the doc
directory of the distribution.
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:
ocamlwrap
tool;OCamlFunctionN<T1, ..., Tn, TR>
(where TR is the return type, while the Ti are the parameter types).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:
LibWrapper.f$closure()
to get the instance associated with top-level function LibWrapper.f
;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) -> stringit 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 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.
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 * stringis 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:
The current version of ocamlwrap
requires that polymorphic variants:
type t = [ `A | `B of int ] val f : int -> trather 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 endis 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:
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; } }
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 endis 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 ... endFunctors 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.tIt 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.MT2mt2 = 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));