--- breaks: false tags: public-tech --- # OCaml でコードを書き始めるチュートリアル この記事は [OCaml Tips Advent Calendar 2022](https://adventar.org/calendars/8396) の一日目です。 OCaml でコードを書くときは [OPAM](https://opam.ocaml.org/) をまず入れておきます。 ``` $ bash -c "sh <(curl -fsSL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh)" ``` でもって、OCaml を入れます。グローバルに入れてもいいですが(`npm -g` 相当)、 プロジェクトごとに OCaml のバージョンやライブラリのバージョンを変えたくなるので、 プロジェクトごとにローカルにインストールすることもできます。ここではそれでいきます。 記事執筆時点で最新の OCaml v4.14.0 を入れます。 ``` $ cd /path/to/project $ opam switch create . 4.14.0 ``` でもってビルドシステムの `dune` を入れます。 ``` $ opam install dune ``` 新しいプロジェクトを始めるためのスケルトンを作ります。 `yourfavname` には好きな名前を入れます。 ``` $ dune init project yourfavname $ cd yourfavname $ tree . . ├── bin │   ├── dune │   └── main.ml ├── dune-project ├── lib │   └── dune ├── test │   ├── dune │   └── yourfavname.ml └── yourfavname.opam 3 directories, 7 files ``` ビルドして実行するには `dune exec` を使います。 ビルドだけなら `dune build` です。テストは `dune runtest` です。 ``` # ビルド $ dune build bin/main.exe # ビルド+実行 $ dune exec bin/main.exe # ビルド+テスト $ dune runtest ``` スケルトンの中身を見てみます。本体は `bin/main.ml` にあります。 ```ocaml= $ cat bin/main.ml let () = print_endline "Hello, World!" ``` この構成では `lib/` 配下にメインのコードをおいて、それを `bin/` 配下と `test/` 配下で使うことを想定しています(多分)。ということで、 `lib` 配下で定義した関数を `bin` で呼び出してみます。 ここで注意すべきなのは、`bin/` の下にあるコードから見えるのは `lib/` にある `Yourfavname` モジュールだけということです[^only-yourfavname-module]。 そこで、まず `lib/hello.ml` に 実行したい関数を定義して `Hello` モジュールを作り、この `Hello` モジュールを `Yourfavname` モジュール内に配置することで、`Yourfavname` モジュール経由で `Hello` モジュールにアクセスできるようにします。 [^only-yourfavname-module]: これに気づくまでに随分時間がかかりました。 ```ocaml= $ vim lib/hello.ml let hello () = "Hello, World!" $ vim lib/yourfavname.ml module Hello = Hello $ vim bin/main.ml open Yourfavname let () = print_endline (Hello.hello ()) $ dune exec bin/main.exe ``` 次に `Hello.hello` 関数のテストを書いてみます。テストフレームワークとして [Alcotest](https://github.com/mirage/alcotest)を使うことにします。 ``` $ opam install alcotest ``` `test/dune` を編集します: - `libraries` stanza で Alcotest を使うこと、及び `lib/` 配下の `Yourfavname` モジュールに依存することを明示します。 - `tests` stanza と `names` stanza を使って、複数のテストをスペース区切りで記述できるようにしておきます。 - `test_hello` という名前のテストを追加します。 ``` $ vim test/dune (tests (names test_hello) (libraries yourfavname alcotest)) ``` テストを書きます。 ```ocaml= $ vim test/test_hello.ml open Yourfavname let test_hello () = assert (Hello.hello () = "Hello, World!"); () let () = let open Alcotest in run "Hello" [ ("hello-function", [ test_case "unit" `Quick test_hello ]) ] ``` テストを動かします。 ``` $ dune runtest Testing `Hello'. This run has ID `DYPGZF4H'. [OK] hello-function 0 unit. Full test results in `/gunjo/anqou/pievyt/test_ocaml/yourfavname/_build/default/test/_build/_tests/Hello'. Test Successful in 0.000s. 1 test run. ``` 一度通ったテストは、その後コードの変更があるまで動かなくなります。強制的に 動かしたいときは `-f` オプションを使います。 ``` $ dune runtest # 何も表示されない $ dune runtest -f Testing `Hello'. This run has ID `0ECRMWON'. [OK] hello-function 0 unit. Full test results in `/gunjo/anqou/pievyt/test_ocaml/yourfavname/_build/default/test/_build/_tests/Hello'. Test Successful in 0.000s. 1 test run. ``` 以下続く記事は、全て上の手順で作ったディレクトリ構造を前提にしています。 ## 参考 - https://gitlab.com/dailambda/plebeia - https://dune.readthedocs.io/en/stable/quick-start.html - https://camlspotter.gitlab.io/blog/2018-08-08-opam-switch/