本記事では、OCamlについて簡単に説明します。ここでは、私が最も優れていると考えるOCamlの機能のみを取上げます。
本記事では、機能を説明する上で使用例はあまり使わず、どちらかというと構文を使っていきます。すべての機能に関する詳細を知りたい方は、『OCaml Document and User’s Manual』、『Real World Ocaml』を読むことをお勧めします。
各機能の説明には、簡単な構文の説明、いくつかの例、そして参考文献のリンクが盛り込まれています。この記事自体は、OCamlの機能を少し味わってみたい方、または特定の機能をもう少し学びたいという方の参考になるでしょう。
ご意見などありましたら、mads379@gmail.comまでご連絡いただくか、@mads_hartmannのTwitterアカウントにご投稿ください。
コードを試してみたい方は、オンラインのREPLが利用できます。
注記: このオンラインREPLは完成版ではありません。パターンマッチング、モジュール、ファンクタ、例外、抽象データ型、一般化代数的データ型など、対応できていないものが多々あります。
目次
- 1. リスト、配列、タプル
- 2. Let束縛
- 3. 関数
- 3.1. 関数の定義
- 3.1.1. ラベル付き引数
- 3.1.2. オプショナル引数
- 3.1.3. デフォルト引数
- 4. レコード
- 5. ヴァリアント
- 5.1. 多相ヴァリアント
- 5.2. ヴァリアントの拡張
1 リスト、配列、タプル
リストは、要素をセミコロンで区切り、角括弧で囲んだ形で書きます。
[ 1 ; 2; 3 ];
- : int list = [1; 2; 3]
配列は、要素をセミコロンで区切り、角括弧と縦線で囲んだ形で書きます。
[| 1 ; 2; 3 |];;
- : int array = [|1; 2; 3|]
タプルは、要素をコロンで区切り、丸括弧で囲んだ形で書きます。
( "foo", "bar", "foobar" )
- : string * string * string = ("foo", "bar", "foobar")
2 Let束縛
Let束縛は、識別子を値に関連付けることができ、次の構文を使って表します。let <識別子> = <式> in <式>。
let hi = "hi " in let there = "there!" in hi ^ there;;
hi there!
^ は、2つの文字列を連結する演算子です。^ は上の例のように中置演算子として使いますが、以下のように、前置演算子とすることで簡単に文を呼び出すこともできます。
(^) "Hi " "there";;
Hi there
Let束縛は、関数を宣言するのにも使われます。次の項目で詳しく説明していきましょう。
3 関数
前の章でご紹介したとおり、関数は、ホワイトスペースで区切られた関数名のあとに引数を加えることによって呼び出すことができます。
min 42 10;;
10
他のプログラム言語では、関数をカンマで区切りますが、ここではホワイトスペースと引数で区切るので、この書き方に慣れるのに少し時間がかかるかもしれません。上記の例を他のプログラミング言語で表すと以下のようになります。
min(10,24);;
- : int * int -> int * int = <fun>
しかしOCamlでは、1つの引数であるタプル(10, 24)を伴うmin関数の部分適用となります。部分適用は、OCamlが備えたすばらしい機能の1つです。この機能は、N個の引数をX個の関数に渡すと、X個の関数-引数Nを返すことができます。OCamlでは、すべての関数でカリー化が可能です。
let at_least_42 = max 42 in at_least_42 22;;
42
3.1 関数の定義
関数は、関数名のあとにホワイトスペースで区切った関数の引数を置いた構文で定義されます。let <関数名> <引数1> <引数2> = <式> in <式>。
次の関数は、1つの引数x (整数と仮定します)があり、整数の2乗値を返します。
let square x = x * x;;
val square : int -> int = <fun>
ラムダ計算をするには、funというキーワードを使って、次の構文で表すことができます。
fun <引数1> <引数2> -> <式>。つまり、上の例は以下で表すことが可能です。
let square = fun x -> x * x;;
val square : int -> int = <fun>
前章でも触れましたが、いくつかの関数は中置演算子で表されます。 ! $ % & * + – . / : < = > ? @ ^ | ~ のいずれかが中置演算子として使われた場合、これらの記号は関数を表すことになります。中置関数や前置関数に関する詳細については、この記事の冒頭で紹介した『Real World OCaml』のこちらの章を読んでみてください。
引数が1つだけの関数にパターンマッチングを利用したい場合には、functionというキーワードが有効です。(これから紹介する実行文はかなり変な構文になっていますが、どうか許してください。)
let rec sum = function | x :: xs -> x + (sum xs) | [] -> 0 in sum [1;2;3;4;5;1;2];;
18
パターンマッチングについては後ほど詳しく説明します。なおこの例文から、OCamlで再帰関数を使う場合にはrecというキーワードを利用することも分かります。
3.1.1 ラベル付き引数
OCamlでは引数の前に ~ を付けることで、ラベル付けをすることができます。ラベルのおかげでコードがより読みやすくなり、引数の順番も気にする必要がなくなります。
let welcome ~greeting ~name = Printf.printf "%s %s\n" greeting name in welcome ~name:"reader" ~greeting:"Hi"
Hi reader - : unit = ()
3.1.2 オプショナル引数
引数の前に ? を付けると、オプショナル引数を作ることができます。オプショナル引数の値は、 Option型の変数として使うことができます。
let welcome ?greeting_opt name = let greeting = match greeting_opt with | Some greeting -> greeting | None -> "Hi" in Printf.printf "%s %s\n" greeting name in welcome ~greeting_opt:"Hey" "reader" ; welcome ?greeting_opt:None "reader"
Hey reader Hi reader - : unit = ()
3.1.3デフォルト引数
オプショナル引数にはデフォルトの値を付与することもできます。先ほどの例文に当てはめてみると、次のように書き変えることができます。
let welcome ?(greeting="Hi") name = Printf.printf "%s %s\n" greeting name in welcome ~greeting:"Hey" "reader" ; welcome "reader"
Hey reader Hi reader - : unit = ()
4 レコード
レコードは、値の集合体をまとめて1つの値として格納するために使われる機能です。下の例文は、2つのコンポーネントが1つのpersonと名付けられたレコードとして定義されたことを表しています。
type person = { name: string; age: int; } ;; let p = { name = "Mads" ; age = 25 } in Printf.printf "%s is %d years old" p.name p.age
Mads is 25 years old- : unit = ()
多相型を使えば、レコードにパラメータをつけることができます。
type 'a ranked = { item: 'a; rank: int; };; let item = { item = Some 42 ; rank = 1 }
val item : int option ranked = {item = Some 42; rank = 1}
レコードに関するより詳細は、『Real World OCaml』の[こちらの章] (https://realworldocaml.org/v1/en/html/records.html)と、OCaml Users Guideのこちらのページをご確認ください。
5 ヴァリアント
ヴァリアントは、代数的データ型としても知られています。通常は再帰的なデータ構造を決定するために使われています。レコードと同じく、多相型を利用してパラメータをつけることができます。以下の例文では、ヴァリアントはが2分木構造を表すために利用されていることが分かります。
type 'a tree = | Leaf of 'a | Node of 'a tree * 'a * 'a tree;; Node ((Leaf "foo"), "bar", (Leaf "foobar"));;
- : string tree = Node (Leaf "foo", "bar", Leaf "foobar")
このtreeという型には、Leaf及びNodeという二つの構成子が含まれています。以下の例文は、このような木構造におけるノード数の最大値を計算する方法の1つを示しています。
let rec depth = function | Leaf _ -> 1 | Node (left, _ ,right) -> 1 + max (depth left) (depth right) in let tree = Node ((Leaf 1), 2, (Node ((Leaf 3), 4, (Node ((Leaf 5), 6, (Leaf 6)))))) in depth tree;;
4
この例文には、以前の章でも紹介したfunctionキーワードが使われています。このキーワードによってパターンマッチングが実行され、引数が1つだけの関数が定義されます。
ヴァリアントはOCamlの中でも最も使い勝手のよい機能の1つです。この機能については特に時間をかけて勉強するだけの価値があります。『Real World OCaml』のこの章を読んで、実際の事例とベストプラクティスについて知識を蓄えておきましょう。
5.1 多相ヴァリアント
OCamlには、多相ヴァリアントと呼ばれる別のヴァリアント型があります。多相ヴァリアントを用いれば、事前に構成子を定義しておく必要がなくなります。多相ヴァリアントとは、特定の目的のために派生した通常のヴァリアントの特別版、と言っても差し支えないでしょう。
let length_of_title book_title = match String.length book_title with | length when length <= 5 -> `Short | length when length <= 10 -> `Medium | _ -> `Long in length_of_title "The Hitchhiker's Guide to the Galaxy"
- : [> `Long | `Medium | `Short ] = `Long
多相ヴァリアント機能に関する詳細も、『Real World OCaml』のこちらの章に詳しく書かれています。
5.2 ヴァリアントの拡張
ヴァリアントの拡張とは文字通り、新たな構成子を追加してヴァリアントが拡張できることを指しています。
これはOCaml 4.02に追加されたばかりの新しい機能なので、実はまだ自分でコードを書いたことがありません。なので、ここでは『OCaml Users Manual』で使われている例文を紹介しておきます。
type attr = .. ;; type attr += Str of string ;; type attr += | Int of int | Float of float ;;
type attr += Int of int | Float of float
詳細については『OCaml Users Manual』のこちらの章をご確認ください。
残念ながらこの機能は『Real World OCaml』が出版された後に追加されたので、この本の中では取り上げられていません。改訂版での解説を待ち望みます。