tech

「プログラミングの基礎」でOCamlを学んで気づいたこと

はじめに

関数型まつり2026をきっかけに、関数型プログラミングに興味を持ちました。イベントでは興味深いプラクティスが多く、実際に学んでみたらおもしろそうだと思いました。

そこで関数型まつりのスタッフで、登壇もしていた同僚のhagaさん(@silasolla)に相談して、『プログラミングの基礎』を読み始めました。本を選ぶ際の相談から学習中のアドバイスまで、hagaさんには大変お世話になりました。ありがとうございます。

本書ではOCamlを使うため、まずはその文法を学びました。この記事では、学習していて気になったOCamlの文法を紹介します。

前提

  • Webエンジニア
  • 普段よく使う言語はGoとTypeScript
  • DartとRubyは商用開発で使用経験あり
  • C・Java・Python・Rustは少し触った程度

演算子や記号の違い

% ではなく mod

OCamlでは、整数の剰余は % ではなく mod を使います。

例えば、5を2で割った余りを求める場合は次のように書きます。

5 mod 2

GoやTypeScriptでは、同じ処理を次のように書きます。

5 % 2

GoやTypeScriptでは % を使ってきました。C・Java・Python・Ruby・Dartでも剰余演算に % を使っていたので、最初は少し違和感がありました。

一方で、以前から % という記号だけでは意味を想像しづらいとも思っていました。そのため、mod の方がより数学的で分かりやすいと感じました。

実数では演算子にも . が付く

実数の演算では、次のように演算子にも . が付きます。

3.0 +. 5.2

整数と実数を厳格に区別するためですが、「毎回演算子にドットを付けることを忘れそう」と思っていました。実際に学習を進める中で、何度も付け忘れました。(忘れそうと思っても、なかなか癖は抜けないもので…)

文字列の連結は ^

文字列の連結には ^ を使います。

"foo" ^ "bar"

冪乗では ^ を使うかなと予想していましたが、浮動小数点数の冪乗では ** を使います。

この点で驚いてXに書いたところ、hagaさんからアドバイスをいただきました!(キャッチしていただいてありがとうございます) OCamlでは中置演算子を自作したり、既存の演算子を再定義したりできると教えてもらいました。

例えば、次のように定義できます。

let ( ^ ) x y = x ** y

この定義により、^ を冪乗として使うことが可能になります(マジすか学園)。

等値比較は =

OCamlの等値比較は = です。 他言語で慣れた == を使ってしまい、文字列の比較で完全にハマりました。 (逆に、他の型ではうまくいってしまった)

OCamlでは、文字列の内容を比較する場合も = を使います。 == は物理的等価性を比較する演算子なので、文字列の内容を比較する用途では = と使い分ける必要がありました。 これにはかなり戸惑いましたが、なんとか覚えることができました(これだけ章をまたいでなお間違え続けた)。

イミュータブルな変数

OCamlの変数は基本的にイミュータブルです。 一部の型は例外らしく、本の後半で出てくるようです。

Rustも変数がデフォルトでイミュータブルなので、するりと受け入れることができました。 関数型のエッセンスを取り込んだ他の言語を扱った経験が役に立ちました。

let で関数を書く

OCamlでは、次のように let を使って関数を定義します。

let f x = x * 2

最初に見たときは「識別子っぽいものが複数並んでいて、関数かどうか分かりづらい」と感じました (冒頭に fn のような関数を示すキーワードがあれば、分かりやすいのになと思いつつ)。 とは言え、実際に使うとそこまで違和感がなかったので驚きです。

一方で、次のような関数の型は理解に苦しみました。

let f years hours = (jikyu + years * 100) * hours
val f : int -> int -> int

そもそも矢印的な記号での表記は、大学の講義以来でした。久しぶりすぎて、思い出すまでにそこそこ時間がかかりました。 そこで、ChatGPTとたくさん壁打ちしながら理解を深めてみました (もう少し読み進めると、ちゃんと書籍にも書いてありました😇)。

私の理解度チェックも兼ねて、解説を少しだけ書きます。 f years の結果が hours を受け取る関数になるため、型も int -> int -> int と表現されます。 また、JavaScriptやTypeScriptで高階関数を使った経験があったため、その経験と結び付けることで少し理解しやすかったです。

if は式として扱われる

OCamlでは、if は式として扱われます。

if 条件 then 式 else 式

then と else の後には式を書き、両方の式は同じ型である必要があります。 if 全体も式なので、その結果を別の式の一部として利用できます。

普段よく使うGoやTypeScriptでは、if は文として扱われているため、 必要な場合は変数への代入などを分岐の中で行います。

普段と異なる表現だったので、if の結果を値として扱える点には少し驚きました。

パターンマッチ・組・レコード

OCamlでは、match を使ってパターンマッチを書くことができます。

match x with
| 0 -> "zero"
| _ -> "other"

パターンマッチは少し独特な気もしましたが、思いの外慣れてしまいました…。過去にTSKaigi 2026のアフターパーティーで登壇した際は、TSらしくないと判断したほど、馴染まないものだと思っていたのですが…!

また、組やレコードも特に違和感なく馴染みました。PythonやRustでタプルを使った経験があり、TypeScriptにはオブジェクト型や Record 型も存在したからです。 既知のデータ構造と対応させながら理解できました。

これまでにも、他の言語を学ぶ際に過去の学習経験とリンクする感覚があり、今回もその経験が役に立ちました。 AI時代には、詳細を知らなくても良いと言われますが、プログラミング言語を学ぶことには今でも意味があると感じました (プログラミングを理解するためにはプログラミング言語を理解している方が良いからです)。

リスト

9章ではリストが出てきました。OCamlでは、リストの先頭に要素を追加するときに :: を使います。

1 :: [2; 3]

このコロン2つ :: はconsと呼ばれます。consはconstructに由来します。

また、リストは他の言語と同様の表現も可能です。

[1; 2; 3]

フォーマッタによって、先ほどのconsを使う形が [1; 2; 3] のような表記に自動で整形されることもあります(この方が好き)。

さらに、:: はパターンマッチでも使います。

match lst with
| [] -> ...
| first :: rest -> ...

式の中ではリストを組み立てるために使いますが、パターンの中では先頭要素と残りのリストに分解します。 この違いは最初に見たとき、「同じ :: なのに役割が変わるのか」と思い、なかなか理解できませんでした。 この仕組みについてはいまだに理解できていないので、別途調査・学習予定です。

細かい話ですが、リストのパターンマッチで網羅できていない場合に、LSP側から警告を出してくれる点も地味にありがたかったです。

再帰関数

OCamlでは、再帰関数を let rec で定義します。

let rec f x =
  ...

再帰関数は久しぶりだったので、最初は感覚を忘れていました。 しかし、大学の講義で学習した感覚は意外と残っていて、演習を2回くらいやるうちに思い出してきました。

大学の講義で使用したC言語では、rec を記載せずに再帰関数を実装できるため、OCamlでは何度も rec を書き忘れました(書き忘れパターン多すぎて草も生えない)。 書き忘れた場合はLSPから指摘されるので、指摘を見てから rec を付けました。LSPに感謝です。

局所変数定義

10章では、次のような局所変数定義が出てきました。

let 変数名 = 式1 in 式2

意味自体は理解できましたが、他の言語で見かけなかったので、独特な書き方に感じました。 (すごくどうでも良いですが、難読クイズにできそうだなとも思いました)

しかし、次のように再帰関数の計算結果を一度保持する用途では、読みづらさを感じませんでした。

let rec gakusei_max lst =
  match lst with
  | [] -> { namae = ""; tensuu = min_int; seiseki = "" }
  | first :: rest ->
      let max_rest = gakusei_max rest in
      if first.tensuu >= max_rest.tensuu then first else max_rest

また、変数名の部分にはパターンを書くこともできます。

これまではパターンマッチを使う機会が多く、値を分解するときは先に match を書く癖を付けました。 そのため、局所変数定義を学んだ直後は、その使いどころに気付かないこともありました。 しかし、何度か使ううちに、「ここは局所変数定義を使う場面だ」と気付けるようになってきました。 11章に入る頃には「意外と局所変数定義を使えてる」と感じるようになり、少しずつ慣れてきました。

再帰関数の適切な使い方を理解できた

『プログラミングの基礎』では、繰り返しの処理をループではなく再帰関数で書いていました。 OCamlにも for 文はありますが、パターンマッチと再帰関数を使って繰り返しを表現しています。

大学の講義で再帰関数を学んだ当時は、プログラミング初心者だったので、少し玄人向けの書き方という印象を持ちました。 しかし、今回学ぶ中で処理対象を小さな単位に分けて考えるアプローチを理解できたため、そこまで玄人向けではなかったと思い直しました。

おわりに

学習を始める前は、関数型言語に少し難しそうな印象を持っていました。 しかし、実際にOCamlを触ってみると、構文自体の難易度は高くありませんでした。 これまでのプログラミング言語と共通する部分も多く、楽しみながら学習できました。

今後も『プログラミングの基礎』を読み進めながら、OCamlを通じて関数型プログラミングを深く学んでいきたいです。

最後まで読んでいただき、ありがとうございました!

参考文献・参考資料