最近 Jane Street が開発している OxCaml を opam-nix から使おうとしたところ意外と動かすまで大変でした。インターネットを放浪しても何も情報が無かったので、ここに残しておきます。動いているコードは GitHub に上がっています。
#oxcaml/opam-repository の追加
まず公式の手順を確認すると opam install
に oxcaml/opam-repository を指定しろと書いてあるので、これに対応する作業を opam-nix で行う必要があります。
これは opam-nix にも説明があるので大して難しくはなくて buildOpamProject'
の repos
に指定すれば良いです。予め Nix Flake の入力としてレポジトリを ox-opam-repository
という名前で読み込んでおきます:
scope = on.buildOpamProject' {
repos = [ox-opam-repository opam-repository];
} ./. query;
#5.2.0+ox のインストール
OxCaml の拡張が入った OCaml コンパイラを導入する必要があるわけですが、これは ocaml-variants
というパッケージのバージョンとして 5.2.0+ox
を指定することになります。これ自体は buildOpamProject'
に渡される query
に指定を含めるだけです:
query = devPackagesQuery // {
ocaml-variants = "5.2.0+ox";
};
ただしこのままだと OxCaml のビルドがコケてしまうので overlay
で derivation を上書きする必要があります。具体的には Nix のビルド環境では /usr/bin/env
が使えないので、
OxCaml のコード中に含まれる /usr/bin/env
を全て ${pkgs.coreutils}/bin/env
に書き換えます。また installPhase
で rsync
が必要になるので nativeBuildInputs
に rsync
を追加します。
overlay = final: prev: {
ocaml-variants = prev.ocaml-variants.overrideAttrs (prevAttrs: {
buildPhase = ''
find . -type f -exec sed -i 's%/usr/bin/env%${pkgs.coreutils}/bin/env%' {} \;
${prevAttrs.buildPhase}
'';
nativeBuildInputs = prevAttrs.nativeBuildInputs ++ (with pkgs; [rsync]);
});
};
これでビルドが通ります。
#LSP や ocamlformat を入れる
OxCaml で用意されている LSP や ocamlformat を入れます。何が用意されているかは oxcaml/opam-repository を見れば書いてあります。
とりあえず LSP と ocamlformat と utop は以下のように書けば入ります:
devPackagesQuery = {
ocaml-lsp-server = "1.19.0+ox";
ocamlformat = "0.26.2+ox";
utop = "2.15.0+ox";
};
#チュートリアルを動かす
OxCaml の最初のチュートリアルの最初に出てくるコードを動かします。
まず parallel
ライブラリが必要になるので *.opam
ファイルに書いておきます:
depends: [
"dune" {>= "3.8"}
"parallel" {= "v0.18~preview.130.36+326"}
]
んでもってチュートリアルを参考に bin/main.ml
あたりにコードを書きます:
let add4 (par : Parallel.t) a b c d =
let a_plus_b, c_plus_d =
Parallel.fork_join2 par (fun _par -> a + b) (fun _par -> c + d)
in
a_plus_b + c_plus_d
let test_add4 par = add4 par 1 10 100 1000
let run_one_test ~(f : Parallel.t -> 'a) : 'a =
let module Scheduler = Parallel_scheduler_work_stealing in
let scheduler = Scheduler.create () in
let monitor = Parallel.Monitor.create_root () in
let result = Scheduler.schedule scheduler ~monitor ~f in
Scheduler.stop scheduler;
result
let () =
let result = run_one_test ~f:test_add4 in
Printf.printf "result: %d\n" result
dune ファイルはこんな感じにしておきます:
(executable
(public_name testoxcaml)
(name main)
(libraries parallel parallel.scheduler.work_stealing)
)
これを dune build
でコンパイルすると、なんとコケます:
❯ dune build
File "bin/main.ml", line 13, characters 54-55:
13 | let result = Scheduler.schedule scheduler ~monitor ~f in
^
Error: This expression has type Parallel_kernel.t -> 'a
but an expression was expected of type local_ Parallel_kernel.t -> 'b
実のところ、まだチュートリアルを全部読まずにこの記事を書いているので、このエラーメッセージを見ても何がなんだかさっぱりですが、とりあえず Parallel.t
を local_ Parallel.t
に書き換えたら通りました:
-let run_one_test ~(f : Parallel.t -> 'a) : 'a =
+let run_one_test ~(f : local_ Parallel.t -> 'a) : 'a =
#まとめ
とりあえず OxCaml 動いたので、これからチュートリアルを読みます。OCaml 5 以降、 OCaml で並列プログラミングができるようになったのはいいんですが、 Rust のような data race に対する静的な保証が無いのが不満だったので OxCaml でそれが緩和されると良いなぁという期待を抱いています。