Introduction

This page describes the CDuce/OCaml interface. This interface allows the programmer to:

  • call OCaml functions from a CDuce module;
  • export a CDuce model as an OCaml module, by giving it an explicit OCaml signature.

The intended usages for the interface are:

  • Reuse existing OCaml libraries, such as database, network, GUI, data structures;
  • Use CDuce as an XML layer (input/output/transformation) for OCaml projects;
  • Develop fully mixed OCaml/CDuce projects.

To see how to build CDuce with support for the OCaml interface, see the INSTALL file from the CDuce distribution.

Translating types

The heart of the interface is a mapping from OCaml types to CDuce types. An OCaml type t is translated to a CDuce type T(t), which is meant to be isomorphic to t: there is a canonical function tT(t) from OCaml values of type t to CDuce values of type T(t), and another canonical function T(t)t.

  • Basic OCaml types char, int, string, unit are translated respectively to Byte = '\0;'--'\255;', -1073741824 -- 1073741823, Latin1 = [ Byte* ], [] = `nil.
  • Tuple types t1 * ... * tn are translated to nested CDuce product types (T(t1),(...,T(tn))...). A function type t -> s is translated to T(t) -> T(s). Labels on the argument of the arrow are discarded.
  • A list type t list is translated to an homogeneous sequence type [ T(t)* ]. An array type t array has the same translation.
  • A option type t option is translated to the type [ T(t)? ].
  • A variant type with a declaration A1 of t1 | ... | An of tn is translated to a type (`A1,T(t1)) | ... | (`An,T(tn)). If a constructor Ai has no argument, the resulting term is `Ai, not (`Ai,[]). Polymorphic variant types are treated similarly.
  • A record type with a declaration { l1 : t1; ...; ln : tn } is translated to a closed record type { l1 = T(t1); ... ; ln = T(tn) }. Mutable fields are just copied.
  • Private variant and record types are treated correctly: the interface never tries to generate OCaml values of these types, but it will happily translate them to CDuce values.
  • A reference type t ref is translated to the CDuce reference type ref T(t). When converting a Caml reference to CDuce, the operation (set,get) on the resulting reference refers to the original reference. However, when converting a CDuce reference to OCaml, the content of the reference is fetched (set), and a fresh OCaml reference is created (copy semantics).
  • The type Cduce_lib.Value.t is translated to the CDuce type Any. The corresponding translation functions are the identity. This can be used to avoid multiple copies when translating a complex value back and forth between CDuce and OCaml. The type Cduce_lib.Encodings.Utf8.t is translated to the CDuce type String. The type Big_int.big_int is translated to the CDuce type Int.
  • A monomorphic abstract type t is translated to the CDuce type !t. This type just acts as a container for values of the abstract type. CDuce never produces a value of this type, and it cannot inspect the content of such a value (apart from checking its type).

The canonical translation is summarized in the following box:

OCaml type tCDuce type T(t)
charByte = '\0;'--'\255;'
int-1073741824 -- 1073741823
stringLatin1 = [ Byte* ]
unit[] = `nil
boolBool = `true | `false
t1 * ... * tn(T(t1),(...,T(tn))...)
t -> sT(t) -> T(s)
t list[ T(t)* ]
t array[ T(t)* ]
t option[ T(t)? ]
A of t | B of s | C(`A, T(t)) | (`B, T(s)) | `C
[ `A of t | `B of s | `C ](`A, T(t)) | (`B, T(s)) | `C
{ x : t; y : s }{ x = T(t); y = T(s) }
t refref T(t)
Cduce_lib.Value.tAny
Cduce_lib.Encodings.Utf8.tString
Big_int.big_intInt

Only monomorphic types are handled by the interface. It is allowed to use polymorphic constructors as an intermediate, as long as the final type to be translated is monomorphic. Recursive types, including unguarded ones (option -rectypes of the OCaml compiler) are accepted. In the following example:

type 'a t = A of int | B of 'a t
type s = int t

type 'a u = A of ('a * 'a) u | B
type v = int u

the type s can be translated, but the type v can't, because its infinite unfolding is not a regular type.

OCaml object types are not supported.

Note that values are copied in depth (until reaching an abstract type, a function types, etc...). In particular, translating an OCaml cyclic values to CDuce will not terminate (well, with a stack overflow!).

Calling OCaml from CDuce

If an OCaml value has a type that can be translated, it is possible to use it from CDuce (see the How to compile and link section for more details).

In a CDuce module, you can write M.f to denote the result of translating the OCaml value M.f to CDuce.

If the value you want to use has a polymorphic type, you can make the translation work by explicitly instantiating its type variables with CDuce types. The syntax is M.f with { t1 ... tn } where the ti are CDuce types. The type variables are listed in the order they appear in a left-to-right reading of the OCaml type. Example:

let listmap = List.map with { Int String }

will return a function of type (Int -> String) -> ([Int*] -> [String*])

Calling CDuce from OCaml

We have seen in the section above how OCaml values can be used from a CDuce module. It is also possible to use CDuce values from OCaml. To do so, you must give an OCaml interface (.mli) for the CDuce module (.cdo). The interface can define arbitrary types, and declare monomorphic values. These values must be defined in the CDuce module with a compatible type (subtype of the translation).

As an example, suppose you have this CDuce module (foo.cd):

type s = (`A,int) | `B
let double (x : Latin1) : Latin1 = x @ x
let dump (x : s) : Latin1 = string_of x

You can define an OCaml interface for it (foo.mli):

type t = A of int | B
val double: string -> string
val dump: t -> string

When the foo.cdo module is compiled, CDuce will look for the foo.cmi compiled interface (hence, you must first compile it yourself with OCaml), and generate stub code, so as to define an OCaml module Foo with the given interface. This module can then be linked together with other "regular" OCaml modules, and used from them.

Notes:

  • It is not mandatory to export all the values of the CDuce module in the OCaml interface.
  • The types defined in the interface cannot (currently) be used within the CDuce module.

How to compile and link

Here is the protocol to compile a single CDuce module:

  • Create a .cmi from your OCaml file with ocamlc -c foo.mli.
  • Compile your CDuce file cduce --compile foo.cd. This command will create a CDuce bytecode file foo.cdo, which also contains the OCaml glue code to export CDuce values as OCaml ones, and to bind OCaml values used within the CDuce module.
  • Compile the OCaml glue code ocamlfind ocamlc -c -package cduce -pp "cduce --mlstub" -impl foo.cdo. The --mlstub option tells CDuce to extract the OCaml glue code from the CDuce bytecode file. You can directly run cduce --mlstub on a .cdo file to better understand how the OCaml/CDuce interface works.
Note: The command cduce --mlstub must find the .cmi file for the unit. You need to add an -I flag if this file is not in the current directory (and additional flags if other .cmi are referenced).

You can then link the resulting OCaml module, maybe with other modules (either regular ones, or wrapping a CDuce module): ocamlfind ocamlc -o ... -package cduce -linkpkg foo.cmo .... The content of foo.cdo is wrapped inside the OCaml glue code; as a consequence, this file is not needed at runtime.

Everything works mutatis mutandis with the native OCaml compiler ocamlopt.

You might need to pass extra -I flags to CDuce so that it could find the referenced .cmi files.

It is possible to run a CDuce module with cduce --run foo.cdo, but only if it doesn't use OCaml values. If it does use OCaml values, you need to produce a custom version of cduce (to compile and run), see: Calling OCaml from the toplevel section.

Calling OCaml from the toplevel

The tool cduce_mktop creates custom versions of the CDuce toplevel/compiler/interpreter with built-in support for some OCaml modules / functions.

cduce_mktop [-I path | -p package | -l unit ... | -byte ] [target] [primitive file]

The target argument is the file name of the resulting toplevel. The primitive file argument points to a file whose contents specify a set of built-in OCaml values to be embedded in the toplevel. Each line must either be a qualified value (like List.map) or the name of an OCaml unit (like List). Empty lines and lines starting with a sharp character are ignored.

The -byte flag forces the creation of the bytecode version of the toplevel (by defaut, the toplevel is produced with ocamlopt).

The leading -I options enrich the search path for OCaml units. The -p options serves a similar purpose; their arguments are findlib package names. All these paths are included in the produced toplevel. The -l options give the OCaml units to be linked in (e.g. x.cmx or x.cmxa)(the -p option automatically include the units).

In a custom toplevel, the directive #builtins prints the name of embedded OCaml values.

Examples

Getting the value of an environment variable

let home = Sys.getenv "home";;

Ejecting your CD with CDuce

This example demonstrates how to use OCamlSDL library.

Sdl.init `None [ `EVERYTHING ];;
let cd = Sdlcdrom.cd_open 0;; 
Sdlcdrom.cd_eject cd;;

If you put these lines in a file cdsdl.cd, you can compile and link it with:

cduce --compile cdsdl.cd -I `ocamlfind query ocamlsdl`
ocamlfind ocamlc -o cdsdl -pp "cduce --mlstub" -impl cdsdl.cdo   \ 
  -package cduce,ocamlsdl -linkpkg

Accessing MySQL

This example demonstrates how to use ocaml-mysql library.

let db = Mysql.connect Mysql.defaults;;

match Mysql.list_dbs db `None [] with
 | (`Some,l) -> print [ 'Databases: ' !(string_of l) '\n' ]
 | `None -> [];;

print [ 
  'Client info: ' !(Mysql.client_info []) '\n'
  'Host info: ' !(Mysql.host_info db) '\n'
  'Server info: ' !(Mysql.server_info db) '\n'
  'Proto info: ' !(string_of (Mysql.proto_info db)) '\n'
];;

If you put these lines in a file cdmysql.cd, you can compile and link it with:

cduce --compile cdmysql.cd -I `ocamlfind query mysql`
ocamlfind ocamlc -o cdmysql -pp "cduce --mlstub" -impl cdmysql.cdo   \ 
  -package cduce,mysql -linkpkg

Evaluating CDuce expressions

This example demonstrates how to dynamically compile and evaluate CDuce programs contained in a string.


let pr = Cduce_lib.Value.print_utf8

try
 let l = Cduce_lib.Cduce.eval 
  "let fun f (x : Int) : Int = x + 1;;
   let fun g (x : Int) : Int = 2 * x;;
   let x = getenv ['HOME'];;
   f;; g;; 
   let a = g (f 10);;
  "
 in
 let _ = map l with
  | ([id], v) -> 
	pr [ !(string_of id) ' = ' !(string_of v) '\n' ]
  | ([], f & (Int -> Int)) ->
        pr [ !(string_of (f 100)) '\n' ]
  | ([], v) -> 
	pr [ !(string_of v) '\n' ]
 in []
with (exn & Latin1) ->
  print [ 'Exception: ' !exn  '\n' ]

If you put these lines in a file eval.cd, you can compile and link it with:

cduce --compile eval.cd -I `ocamlfind query cduce`
ocamlfind ocamlc -o eval -pp "cduce --mlstub" -impl eval.cdo -package cduce -linkpkg

Use CDuce to compute the factorial on big integers

(* File cdnum.mli: *)

val fact: Big_int.big_int -> Big_int.big_int


(* File cdnum.cd: *)

let aux ((Int,Int) -> Int)
 | (x, 0 | 1) -> x
 | (x, n) -> aux (x * n, n - 1)

let fact (x : Int) : Int = aux (Big_int.unit_big_int, x) 
  (* Could write 1 instead of Big_int.unit_big_int. Just for fun. *)