CDuceのOCamlとのインタフェース

導入

このページでは、CDuce/OCamlインタフェースについて説明する。このインタフェースによって、次のことができるようになる。

インタフェースの意図される使用法は、次のものがある。

CDuceをOCamlインタフェースのサポート付きでビルドする方法を知るには、CDuceの配布物のINSTALLファイルを参照すること。

型の翻訳

インターフェースの中心は、OCamlの型からCDuceの型へのマップである。OCamlの型tは、tの同形であることを意味するCDuceの型T(t)に翻訳される。OCamlの型tの値からCDuceのT(t)型の値に変換する標準関数t → T(t)があり、また別の標準関数T(t) → t

標準の翻訳を、次の表にまとめた。

OCamlの型tDuceの型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

このインタフェースは単相型だけを扱う。多相コンストラクタは、最終的な型が単相に翻訳される限りにおいて、中間物として使うことができる。再帰型はガードされていないもの(OCamlコンパイラのオプション-rectypes)を含め、受理される。例、

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

において、型sは翻訳できるが、型vはできない。無限の展開が通常の型にならないためである。

OCamlのオブジェクト型はサポートされていない。

値は徹底的に(抽象型、関数型などに達するまで)複製されることに注意。特に、OCamlの循環値をCDuceに翻訳すると、終了しない(スタックオーバーフローになる)。

OCamlをCDuceから呼び出す

OCamlの値が翻訳できる型を持っていれば、CDuceから使用することができる(詳しくは、コンパイルとリンクの項を参照)。

CDuceのモジュールにおいて、M.fを書いて、OCamlの値M.fをCDuceに翻訳した結果を意味することができる。

使いたい値が多相型であれば、CDuce型の型変数を明示的にインスタンス化することで変換が動作するようになる。構文は、tiをCDuceの型としてM.f with { t1 ... tn }である。型変数はOCaml型を左から右へ読んだときに現れる順序で列挙する。例:

let listmap = List.map with { Int String }

は、関数型(Int -> String) -> ([Int*] -> [String*])を返す

CDuceをOCamlから呼び出す

上の項でOCamlの値をCDuceのモジュールから使うことができることを見てきた。CDuceの値をOCamlから使うこともできる。そうするためには、CDuceのモジュール(.cdo)に対するOCamlのインタフェース(.mli)を与えなければならない。インタフェースは任意の型と単相型の値を定義できる。これらの値はCDuceのモジュール中で互換性のある型(翻訳の部分型)で定義されていなければならない。

例えば、次のようなCDuceのモジュールを考える(foo.cd)。

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

これに対して、次のようなOCamlのインタフェースで定義することができる(foo.mli)。

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

foo.cdoモジュールがコンパイルされていれば、CDuceはfoo.cmiコンパイル済みインタフェースを探し(よって、最初に自分でOCamlでコンパイルしておかなければならない)、与えられたOCamlのモジュールFooを定義するスタブコードを生成する。そうすると、このモジュールは、ほかの"普通の"OCamlのモジュールと一つにリンクすることができるようになる。

注意:

コンパイル及びリンクの方法

ここで、CDuceのモジュール一つをコンパイルする方法を示す。

注意: コマンドcduce --mlstubは、ユニットに対する.cmiファイルを見つけなければならない。このファイルがカレントディレクトリにないならば-Iフラグを(また他の.cmiが参照されればさらにフラグを)加える必要がある。

必要な変更を加えれば、ネイティブOCamlコンパイラocamloptでも全て動作する。

参照される.cmiファイルが見つけられるように、-Iフラグを追加で渡す必要があるかもしれない。

CDuceのモジュールをcduce --run foo.cdoで実行することができるが、OCamlの値を使っていないときのみである。もしOCamlの値を使っていれば、(コンパイル及び実行に)カスタム版のcduceを生成する必要がある。参照: OCamlをトップレベルから呼び出す

OCamlをトップレベルから呼び出す

ツールcduce_mktopは、OCamlモジュール/関数へのビルトインサポートが付いた、カスタム版のCDuceトップレベル/コンパイラ/インタプリタを生成する。

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

target引数は、結果としてできるトップレベルのファイル名である。primitive file引数は、その内容でトップレベルに埋め込むべきビルトインのOCamlの値の集合を指定しているファイルを指す。各行は(List.mapのような)修飾された値か、(Listのような)OCamlのユニットの名前かのいずれかでなければならない。空行や、シャープ文字で始まる行は、無視される。

-byteフラグは、バイトコード版のトップレベルを生成ことを強制する(デフォルトではトップレベルはocamloptを使って生成される)。

最初の-Iオプションは、OCamlのユニットに対する検索パスを追加する。-pオプションは、同様の目的のために提供されている。これらの引数亜h、findlibパッケージ名である。これらのパスはトップレベルを生成するときにインクルードされる。-lオプションは、リンクすべきOCamlのユニット(例えば、x.cmx又はx.cmxa)を与える(-pオプションは、自動的にユニットをインクルードする)。

カスタムトップレベルは、命令#builtinsで組み込みのOCamlの値の名前を印字する。

例</h2>

環境変数の値を取得する

let home = Sys.getenv "home";;

CDuceでCDを取り出す

この例ではOCamlSDLライブラリの使い方を実演している。

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

ファイルcdsdl.cdにこのコードを入れれば、次のようにコンパイル及びリンクができる。

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

MySQLにアクセスする

この例では、ocaml-mysqlライブラリの使い方を実演している

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'
];;

ファイルcdmysql.cdにこのコードを入れれば、次のようにコンパイル及びリンクができる。

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

CDuceの式を評価する

この例では、文字列を含んだCDuceのプログラムを動的にコンパイル及び実行する方法を実演している。

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' ]

ファイルeval.cdにこのコードを入れれば、次のようにコンパイル及びリンクができる。

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

CDuceを大きな整数の階乗を計算するのに使う

(* 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. *)