40歳から始める関数型言語、OCaml

というわけで、今年に入ってからのここ数ヶ月、OCamlを勉強し始めている。

動機

これまで「関数型言語をちゃんと触ったことがない」ということが若干コンプレックスになっていた。40歳になった今、唐突に始めてみる気になったので、やることにした。

Why OCaml

そもそも関数型にどんな言語があるのか、それぞれがどんな特徴であり、どういったところで使われているのか、という情報すらほとんど知らなかったので、「関数型言語やってみたいんですけど、どれがいいんすかね?」と某所で雑に相談したところ、OCamlを挙げてもらったので、素直にそれに従ってみることにした。

そんな適当で良いのか?とも思うけど、とにかくどれであっても触ってみないことには何も分からないし。実際、OCamlを選んで間違いは無かったと今は思う。

学習方法

Real World OCaml

とにかくOCamlの学習にはこれが良いらしい、という話をきいた。

dev.realworldocaml.org

ので、これを最初の数章は頑張って読んでみて、あとは実際にコードを書いてみつつ必要になったら参照して勉強していく、という感じで。 実際とても詳しく丁寧に書かれていて、とても良い本だと思う。

Github Copilot と ChatGPT

これらはもう現代のプログラマにとって欠かせないものになっていると思う。

最近のメジャーな言語と比較してOCamlに関する学習データがどの程度の量と質で、どの程度優れたコード出力能力であるのかは分からないが、少なくとも初心者から始める人間にとっては十分に助けになる。

特にChatGPTは、環境構築からライブラリの使い方などは勿論、「Rustでこう書いてたのはOCamlではどう書くの」とか「こういうのできないの」「こう書いてみたけど他にも書き方あるのかな」みたいな、独り言で呟くようなことでもChatで気軽に書いて質問できて体験が良かった。 めちゃくちゃ親しくて時間を奪うこと気にせずに何でも質問できる友人、が隣に居ればそういう人に頼れるかもしれないが、そういう友達が居ない人間にとってはこうした心理的なハードル低く幾らでも質問できる存在はとても有り難いと思う。

とはいえやはり正確性は保証されなくてChatGPTも平気で実在しないライブラリ関数をでっちあげて使用例を提示してきたりするし、油断せずにちゃんと公式ドキュメントなど探して確認しながら書く必要はある。 このへんは仕方ないと腹を括って、「俺が学習データを提供してやるぜ」くらいの気概でできるだけ良い(と自分が思う)コードを書いてとにかく公開していく。

オンラインジャッジ (競プロ)

簡単なコード片を書いて正しい入出力を得られたか確認できる、また別の人の書いた同じ目的のコードを気軽に参照できる、という点で競技プログラミングの過去問などは良い学習材料になると思う。とりあえず他人と競うことは考えず、オンラインジャッジシステムとして利用させてもらう。

個人的には LeetCode でやりたかったが、LeetCodeにはOCamlの選択肢が無い…。ので、 AtCoder を使うことにした。 AtCoder Problems の「Boot camp for Beginners」 というのがあることを @naoya_ito さんのTweet で知ったので、真似してこれらを解いてみることにした。

とはいえAtCoderOCamlのランタイムや使えるライブラリが少し古くて(2023/04時点)、手元で Core 最新を使って動くコード書けて意気揚々とSubmitしてみたら CE (Compilation Error) 出てガックリすることも。 ここは今後 言語のアップデート で更新されてより使いやすくなることを期待している。

簡単な問題を解くことで基礎的なデータ構造やアルゴリズムの扱いは練習できるが、難易度が上がると当然 数学的な思考力やより高度なアルゴリズムの知識が主眼になってくる。 そこは言語習得とはまた別の話になってくるのでそこまで頑張る必要はない(本当に競プロを頑張るならC++とかRustとか他の言語でやったほうが良いと思う)、ということでほどほどにして止めるつもり。

Advent of Code

より実践的に「プログラミングによる問題解決」をできるようにする練習として、 Advent of Code は非常に良い題材だと思う。 AtCoderのような競技プログラミング的な要素もあるが、それだけではなく

  • 入力データが必ずしも扱いやすいものばかりではない
    • → 様々な形式に対応したparse処理を書く練習になる
  • part1/part2 で同じ入力から異なる解を出す、など出力も様々な型・形式になり得る
    • → interfaceを考える練習になる
  • アルゴリズムだけで解決しないこともある
    • → 力技での膨大な探索や泥臭い実装などが必要になることも

といった点でまた別の実装力を鍛えることができると考えられる。

2022を完走した記事 にも書いたが、2022年の問題は特にバランス良く様々な題材のものがあって言語習得の練習に十分に適している、と感じた。

というわけで、OCamlAdvent of Code 2022 を解くチャレンジをしている。

github.com

まだ18日目までだが、どうにか最後まで頑張りたい…!

その次?

やはり「問題を解く」だけではなく、最終的には「何らかのアプリケーションを自作する」ことができるくらいにはなりたいので、それを考えていく。

自分の場合は「過去に他の言語で作ったことがあるもの」「自分が使いたいもの」を作るのが最もモチベーションが高くなるので、そういうもので考えると、、、今はコンピュータ将棋関連だろうか。 幸い(?) opam を検索してみた限りではビットボード実装や将棋関連のライブラリなどは存在していないようなので、自分で作ってみるのは面白そうだ。関数型言語での将棋プログラムなんて全然需要は無さそうだけどw とりあえずOCamlでの実装がC++やRustと比較してどの程度の速度になるのかは知りたいので perft できるくらいまではやってみたい。

あとは今の流行でいうと Bluesky で使われている AT Protocol の実装をしてみる、とかだろうか。これもわざわざOCamlでやろうとする人はあんまり居なそう…

所感

とりあえず数週間触ってみての感想。

|> は多用することが分かったので自作キーボード(Claw44)のキーマップにマクロとして追加した。

関数型という概念

結局、現状どれくらい理解できているだろうか…。まだまだ理解が浅いとは思う。

基本的に Base ライブラリを使用しているが、 List に対する様々な処理が再帰で表現できるというのを見て驚かされた。というか List というデータ構造がこんなにも使いやすくて色々できるんだなぁ、と少し親しみを持てるようになった。 とりあえず、 List などを使って |> でパイプを繋げてデータを変換していく、という操作は慣れてきた。

最初はどうしても「手続き型の言語だと普通こう書くので、それを関数型で表現すると…」と変換していく感じにはなりがちだったが、だんだん「この関数とこの関数を組み合わせてこういう関数を作って、そしてこの入力にこういう出力を返す関数を作る、」という感じで考えられるようになってきた気がする。

高階関数の部分適用とか使ってみるとめっちゃ便利だな〜と感心したり。

プログラミングHaskell という本を何年か前に買ったものの序盤ですぐに挫折してしまっていたのだけど、OCamlを通じて多少なりとも理解が深まってきたおかげか、今は再チャレンジして楽しく読めている。

OCamlの書き味

思っていた以上にツールチェインなどが整っていて、環境構築からエディタ設定などほとんど問題なくできて良かった。 VSCodeでは OCaml Platform 入れるだけだし、 ocamlformat を使って自動整形もできる。このあたりの「揃っていて欲しい」ものは大抵ちゃんと揃っている。

コンパイラのエラーメッセージは正直あまり親切な感じはなくて、型が合わないのはどこをどうすれば… みたいなのがなかなか詰まりやすかったりした。とはいえ「ちゃんとコンパイルが通ればだいたいちゃんと動く」という感覚がRustに近いものがある、と感じる。

あとは dune がRustでいう cargo と同じような感覚で使えるので、これも便利。

Rust, Python の経験

Rustを書いたことがあると、色んなところで「似ているな」と感じるところがあった。例えば option とかそれに対するパターンマッチとか。 Rustのiteratorをメソッドチェーンで繋いで処理するのと同じような感覚で |> で繋ぐ処理を書けるな、とか。 Rustがまったくの未経験だったらもっとOCamlに戸惑っていたかもしれない。

Pythonはそんなに似てないかな…? Haskellみたいに内包表記があるともっと違う感想になったかな。 ただOCamlの影響で自分が書くPythonコードがちょっと変わってきたような気がする。関数の使い方をより意識するようになったというか、なんというか。上手く言語化できないけど。

関数型言語をやるとプログラミングが上手くなる」という言説を聞いたことがあるが、こういうところで少しは効果がでるのかな…?気のせいかもしれない。

AIとの親和性

ところで Github Copilot にコードの補完をしてもらうときに、関数のシグネチャの情報はわりと重要だと思っていて。 「こういう名前の関数でこの型のこの名前の引数を受け取って、この型の値を返す」という情報が書いてあると当然補完も正確になりやすいだろう、と。

一方でOCaml型推論が強力なので、型はあるもののtype annotationを書く必要が殆どなく、またちょっとした処理は無名関数を書くことが多いし、そうでなくとも let rec loop acc x = ... とか let f x y = ... とか、簡潔な書き方になりがち。 そうすると、補完しようとするAIとしては情報が足りなすぎて難しいよなぁ、ということを思った。

明瞭な関数名や型注釈をいちいち書くのは面倒で人間としては省略できる方が有り難いが、それはそれでAIに助けてもらいながら書く場合には意図や情報を渡せるという点で良いのかもしれない。 今後新しく作られる言語の設計もそういう観点が考慮されるようになってくるんだろうか、とか。

まとめ

コードを書く仕事はAIに奪われていくかもしれないが、プログラミングは楽しいので好きなように書きたいだけ書いたら良い。