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.
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.
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 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 |
- | - |
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 OCamlTuple
N, 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 OCamlTuple
N does not inherit from any of the class OCamlTuple
K 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:
OCamlFunction
N<
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)
. OCamlFunction
N classes are akin to OCamlTuple
N 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
;OCamlFunction
N 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 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 * 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:
The current version of ocamlwrap requires that polymorphic variants:
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:
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<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.