blog.anqou.net
rss
author
tags

OxCaml を opam-nix から使う

最近 Jane Street が開発している OxCamlopam-nix から使おうとしたところ意外と動かすまで大変でした。インターネットを放浪しても何も情報が無かったので、ここに残しておきます。動いているコードは GitHub に上がっています。

#oxcaml/opam-repository の追加

まず公式の手順を確認すると opam installoxcaml/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 に書き換えます。また installPhasersync が必要になるので nativeBuildInputsrsync を追加します。

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.tlocal_ 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 でそれが緩和されると良いなぁという期待を抱いています。