2019年末からOCamlの勉強を初めていました。
自分の中でプログラミング言語が「とりあえず使えるようになった」という一つの目安として、以下の点があります。

  • CRUDとログイン機能を備えたシンプルなWebアプリケーションが作れる
  • 本番運用が出来る(デプロイとかビルドとか)
  • その本番環境の再構築(バックアップ/リストア含む)ができる

自分はWeb開発者として生きているので、基本的にはWebアプリケーションが作れるかどうか、ということが最も重要なファクターです。
そして今回、実際にこのブログのバックエンドをOCamlでリプレースすることができました。
これで、自分の中では取りあえず「OCamlでシンプルなプログラミングできます」と言える状態になりました。

なぜOCamlなの?

もともと、2018年末頃から「JVMで動く以外の言語を勉強したい」と思い始めていました。
そこで、当時Flutterで人気がどんどん出てきていたDartを使ってこのブログのバックエンドを書き換えました。
Dart自体はとても良い言語でしたが、頻繁にビルドできなくなるなど(ライブラリのバージョン周りが原因)、運用していてイライラすることが多く、他のプログラミング言語を探し始めました。

次に勉強する言語は、少なくとも以下の要件を満たしているものに絞りました。

  • 強く静的型付けされた言語
  • VMが必要となるような言語でない
  • パイプライン演算子がある

こうなるともうOCamlしか選択肢はありませんね!
(本当はElixirにも興味は有ったのですが、VMで動くし静的型付けされていないため、候補から外しました。)
他にもCrystalやJuliaを少し触ってみましたが、Crystalはコンパイルが遅い、そしてJuliaはWebアプリケーションとして使うと遅いなどなど、なかなか自分の希望する要件にはマッチしませんでした。

OCamlどうなの?

個人的には凄く好きです。
とはいえ、具体的に何が良いか、ということが語れるほど習熟していませんが。
ただ、 OCaml自体は普通に使う分にはそこまで難しい言語ではない のかな?と思っています。
実行速度もコンパイルも、驚きの速さです。
クラスを使わずに型の定義が出来るし、その型定義も1ファイルに1つだけ、といった制限もないのでカジュアルに型の宣言ができます。

バリアントを使うと本当に短いコードで複雑なデータ構造を表現することができます。

たとえば、OCamlで以下のような2分木を表現するコードを考えます。

     A
   /   \
  B     C
 /    /   \
D    E     F

OCamlではこういったデータ構造を型として表現できます。

type 'a tree = Leaf | Branch of 'a * 'a tree * 'a tree;;

そして実際の使い方は

let char_tree =
    Branch('A', Branch('B', Branch('D', Leaf, Leaf), Leaf),
    Branch('C', Branch('E', Leaf, Leaf), Branch('F', Leaf, Leaf)));;

となります。

たった1行の型の宣言で、2分木を表現できるデータ構造が出来上がりました。
こういった型の使い方が、OCamlを始めとした型に強い言語の最も大きな魅力だと思います。
なお、私は型プログラミングにまったく明るくないので上記のコードを理解するのに数時間かかりました。

また、関数が標準でカリー化されているので、コレは上手く利用するとかなり柔軟なアプリケーションが構築できるような気がしています。
実際にデータベース周りでトランザクションを走らせてSQLを実行する際にカリー化された関数の便利さを痛感しました。
この辺り、いつかOCamlの入門記事などを書くことが有ればなんとか説明できるようにするつもりです

開発環境

私は開発にvimを使っていますが、OCamlの補完用ツールであるMerlinが最高に便利です。
補完は当然として、型指定せずに束縛している値などの型情報の表示など、コードを書く上で必要なものはこのツールで十分だと思います。
IDEを使わずにココまで安心して気持ちよくコードが書ける言語は自分的には初めてでした。

また、utopという所謂REPL環境も非常に使いやすく、型からちゃんと補完もしてくれるので非常に快適に実験コードが書けます。

OCamlの辛いところ

ドキュメントの少なさ

自分がOCamlの学習で躓いたのはもうドキュメントの少なさ、コレです。
基本的にはライブラリの使い方などもすべてソースを読むことになります。
強く静的型付けされた関数型言語であるOCamlですので、確かにソースを読めば使い方が分かる、というのはあながち間違っていません。
が、OCaml入門の時点でコレを強制されるのはかなり辛かったです。(未だに辛い)

ビルド周りが辛い

また、それ以上にOCamlのビルド周りは本当に地獄です。
今はDuneというビルドツールがデファクトスタンダードになっていて、実はドキュメントが存在します!(https://dune.readthedocs.io/en/stable/

しかし、公式ドキュメントどおりの説明で動かない、ということが多々あります。ありすぎてもうメモすることを止めてしまいました。
もともとOCamlにはocamlcocamloptという2つのコンパイラがあり、さらにコンパイルすると複数のファイルが生成されます。
それらを依存関係順に並べてビルドして。。。という非常に面倒くさい手順を覆い隠してくれるのがビルドツールDuneです。
確かに本当に楽を出来るのですが、少し込み入ったことをしようとすると本当につまづきます。

安心してOCamlの開発をするのであれば、まだocamlcocamloptといった専用ツールでのビルドやリンクなどの仕組みを理解しておく必要が有ると思います。

私はPostgreSQLのlibpqを静的リンクしようとしていましたが、エラーも出ずに単純に静的リンクされ無いので調査のしようも無く、最終的には諦めました。

でもコーディングには関係ない

そもそも、ビルド周りはOCamlに限らずC言語由来のライブラリを利用する言語や環境だと同様の大変さが有るようです。
私は今までPHPとJVM畑で生きてきたので、この辺りの知識が皆無で本当に大変でした。
なので、OCaml自体が辛いというよりは、Linux上でアプリケーションをビルドする際の諸々の知識が無くて苦労した、といったほうが正しいかもしれません。

フレームワーク

今回は、OpiumというSinatraライクなフレームワークを利用しました。
当然シンプルな機能のみの提供なので、ORM等の機能はありません。
また、ログイン認証やCORSの取り図らい等の機能もないので、全て自分で実装する必要があります。

この辺り、今までライブラリやフレームワーク任せだった部分を自分で実装したりしたので大分時間がかかりましたが、かなり勉強になりました。

その他の変更

以下OCamlは関係ない内容です

Dockerイメージの整理

今まで面倒くさくてちゃんと専用のDockerイメージをつくっていませんでいたが、今回はデプロイを楽にするためにちゃんとAPIとフロントエンド用のコンテナはDockerfileを作成しました。

LocalStorageからCookieへ

今まではJWTトークンをLocalStorageで管理していましたが、セキュリティ的に駄目ね、ということでCookieベースに移行しました。
クライアント側のNuxt.jsでSSRする際には結局Cookieを使用していたので、結果的にSSR時でもブラウザ上でもCookieから情報を取得する、という事に統一できてスッキリしました。

OAuthとか

そもそも認証機能はTwitterやらのOAuthを使っても良いのかな?とも思いましたが、他サービスに認証基盤を任せると、そちらでアカウントが削除されたりすると関係ないサービスにまで影響が及んでしまうので、今後自分が関わるサービスでは原則導入しないつもりです。(本当に必要ならする)
そもそも、個人情報を何かのサービスに預ける、という考え方が変わる可能性も有るのではと考えています。
近い将来、全人類が自前のサーバのようなものを保持して、利用するサービスはその個人情報にアクセスしに来る、という感じになるかもしれません。

そういったことに想像をふくらませる切っ掛けを持てたのは、OCamlの学習の間接的なメリットでした。

今後の予定

とりあえず、OCamlとしてはテスト周り、多相バリアント、ファンクタを重点的に勉強していこうと思っています。
フロントエンドに関しては、もうSSRは不要だと考えているので、Nuxt.js自体利用しないようにする予定です。
abで試して気付きましたが、SSR時のサーバの負荷が凄いです。そして当然応答が物凄く遅くなります。
SSRのメリットが現状SNS向けのOGPぐらいなので、コレは普通にAPIサーバがテンプレートとなるHTMLを返すようにして、そのタイミングでogタグを生成してしまうのがベストだと考えています。
そのテンプレートとなるHTMLはJavaScriptとCSSを読み込むようにしておけば、普通にSPAとして動かせるしデプロイもAPIとフロントエンドで分けられて良い感じな構成に出来ると思います。