はじめの一歩
OCamlコードの実行
TryOCamlのおかげで、ブラウザで対話型セッションを 実行するのが最も簡単な始め方です。
OCamlをコンピューターにインストールするには、Install ドキュメントを参照して下さい。
素早く小さいOCamlの式を試すには、対話型のトップレベルまたは、REPL(Read-Eval-Print Loop) を使うことができます。
ocaml
コマンドは、基本的なトップレベルを提供します。
(システムのパッケージマネージャーを通してrlwrap
をインストールし、
ヒストリーナビゲーションを得るためにrlwrap ocaml
と実行したほうが良いです。)
ocaml
コマンドをOPAMまたは、システムのパッケージマネージャーを通して
インストールした場合は、代わりにutopトップレベルを使う事をおすすめします。
基本的なインタフェースは同じですが、より便利に使うことができます。(ヒストリーナビゲーション、オートコンプリート等)
各ステートメントが終わったことを示すには、;;
を使います。 これは、ocaml
実行中の様子です。:
$ ocaml
OCaml version 4.10.0
# 1+1;;
- : int = 2
これは、utop
を使って同じコードを実行中の様子です。:
───────┬────────────────────────────────────────────────────────────┬─────
│ Welcome to utop version 1.18 (using OCaml version 4.02.3)! │
└────────────────────────────────────────────────────────────┘
Type #utop_help for help about using utop.
─( 10:12:16 )─< command 0 >───────────────────────────────────────────────
utop # 1 + 1;;
- : int = 2
my_prog.ml
という名前のOCamlプログラムをネイティブの実行形式へコンパイルするには、ocamlbuild my_prog.native
を使います:
$ mkdir my_project
$ cd my_project
$ echo 'let () = print_endline "Hello, World!"' > my_prog.ml
$ ocamlbuild my_prog.native
Finished, 4 targets (0 cached) in 00:00:00.
$ ./my_prog.native
Hello, World!
詳細に付いては、Compiling OCaml projectsを参照して下さい。
コメント
OCamlのコメントは(*
と *)
で囲まれた部分である。
(* これは一行だけのコメント *)
(* これは
* 複数行に渡る
* コメント
*)
別の言い方をすると、コメントの仕方はCに昔からあるコメント文によく似ている。(/* ... */
)
また (Perl の #...
や C99, C++, Java の //...
のような)
行コメントの文法はない。
Ocaml では (* ... *)
のネスト(入れ子)が許されており、
コードのある範囲をまとめてコメントアウトすることも簡単にできる。
(* このコードは作りかけ…
(* 素数テスト *)
let is_prime n =
(* 自分へのメモ: これをメーリングリストで聞くこと *)XXX;;
*)
関数呼び出し
さて、何か関数を定義したとしよう。例えばrepeated
という関数を定義したとする。これは文字列s
と整数n
を引数にとり、s
をn
回繰り返した新しい文字列を返す関数とする。
Cに由来するほとんどの言語では、この関数を呼び出すときにはこのようにする。
repeated ("hello", 3) /* Cコード*/
これは、「関数repeated
を2つの引数により呼び出す。最初の引数は文字列helloであり、二番目の引数は3である」ことを意味する。
ところが、OCamlやその他の関数型言語では関数呼び出しを違ったように書く。特に括弧のつけ方が異なり、間違いの元になりやすい。上と同じ関数呼び出しをOCamlで書くと、
repeated "hello" 3 (* OCamlコード *)
括弧がないこと、引数の間のコンマがないことに注意しよう。
ややこしいことに、repeated ("hello", 3)
はOCamlでちゃんとした意味がある。これは「関数repeated
を一つの引数で呼び出す。引数は文字列helloと整数3の組である。」を意味する。もちろんこれは誤りである。repeated
は2つの引数を取り、しかも最初の引数は文字列であって組ではないからだ。しかしとりあえず組(tuple)については考えないことにしよう。まず引数のまわりを括弧でくくらないこと、引数をコンマで分けないことを覚えよう。
さて次に別の関数を考えよう。get_string_from_user
はユーザーから文字列の入力を受け、入力された文字列を返す関数である。この関数の戻り値をrepeated
に渡したい。CとOCamlのコードを比較しよう。
/* Cコード: */
repeated (get_string_from_user ("Please type in a string."), 3)
(* OCamlコード: *)
repeated (get_string_from_user "Please type in a string.") 3
括弧のつけ方とコンマの有無に注意してほしい。一個で言うと「関数呼び出し全体を括弧でくくる。引数の周りには括弧はつけない。」ということになる。さらに例を示そう。
f 5 (g "hello") 3 (* fの引数は3つ。gは一つ *)
f (g 3 4) (* fの引数は一つ。gは2つ。*)
# repeated ("hello", 3) (* エラーになる。 *);;
Error: This expression has type 'a * 'b
but an expression was expected of type string
関数定義
既存の言語で関数(Java を嗜んでいるのなら静的メソッド) を定義する方法はご存じでしょう。 では、OCaml ではどうするのでしょうか?
OCaml の文法は晴れ晴れするほどすっきりしています。 二つの浮動小数点数を引数にとって平均を計算する関数があります:
let average a b =
(a +. b) /. 2.0;;
OCaml の"トップレベル" (Unix なら ocaml
コマンドをシェルプロンプトから入力してください)
にこれを入力すると、こうなります:
# let average a b =
(a +. b) /. 2.0;;
val average : float -> float -> float = <fun>
この関数定義を良く見て、また OCaml がどういう応答を返したのかを 見るといろいろと疑問に思う点が出てくるでしょう。
- このコードの演算子の後ろについている
.
は何? float -> float -> float
とかいうの、これ何?
次節でこれらの疑問に答えていきますが、その前にまず、
この関数と同じものを C で定義します (Java も C と似たようなものです)。
するともっと疑問が浮かぶかも知れません。 C バージョンの average
:
double average (double a, double b)
{
return (a + b) / 2;
}
先に示した OCaml 版のとても短い関数定義と見比べましょう。 こんな疑問が浮かびませんか?:
- OCaml 版で
a
やb
の型宣言をしなくていいの?OCaml はどうやって型を知るのですか?(OCaml は本当にその型を知っているのですか?完全に動的型なのですか?) - C では
2
は暗黙の型変換でdouble
に変換されますが、OCaml ではどうしてそうならないのですか? return
を OCaml ではどう書くのですか?
じゃぁ答えていきましょう。
- OCaml は強い静的型付けされた言語です。(言い換えると、Perl のような動的型ではありません)
- OCaml は型推論によって型を抽出するので、明示する必要はありません。先の通り OCaml の トップレベルで用いれば、OCaml は入力した関数の型を(OCaml がどう推論したのかを)教えてくれます。
- OCaml
には暗黙の型変換はありません。もし浮動小数点数を使うのであれば、
2
は整数型なので2.0
と書く必要があります。 - OCaml
には演算子の多重定義は禁止されているので、「整数の足し算(
+
)」 と「浮動小数点数の足し算(+.
)」(付随するピリオドに注意)とで異なる演算子が用意されています。他の算術演算子も同様です。 - OCaml は関数の最後の式を返値とするので、C のように
return
を書く必要はありません。
詳細は次の節、章に示します。
基本の型
OCaml の基本型は以下の通りです。
OCaml の型 範囲
int 32ビットCPU では 31ビット符号付き整数 (およそ±10億)、64ビットCPU では 63ビット符号付き整数
float IEEE 倍精度浮動小数点数、C の double と同じ
bool true もしくは false となる真偽値
char 8bit 文字
string 文字列
unit () と書くもの
OCaml では整数型(int
)の 1ビットを
自動的なメモリ管理(ガベージコレクション)のために内部的に使っているため、
int
型が 32ビットでなく 31 ビット(64ビット環境なら
63ビット)となっています。
この違いはごく特定のケースの除いて問題にはならないでしょう。
たとえばループの回数を数える場合、OCaml では 20 億ではなく10億までしか
カウントできないことになりますが、
どんな言語でもこの限界近くでカウントするのなら、巨大数を扱える型 (OCaml
なら Nat
や Big_int
モジュール)を使うべきであるので、
この制限が問題になることはないでしょう。 とはいえ、32bit
整数型を使いたいとき
(例:暗号コードやネットワークスタックなどを書くとき)には、 OCaml
にはあなたの環境の普通の整数型に相当する nativeint
型があります。
OCaml には符号なし整数型は基本型としてはありませんが、 nativeint
型を使えば同じことができます。 あと、OCaml
には単精度の浮動小数点数は存在しません。
OCaml には文字型として char
型があり、 'x'
などと書きます。
残念ながら char
型では Unicode、UTF-8 はサポートしていません。 これは
OCaml で対応してしかるべき重大な欠陥ですが、 かわりに comprehensive
Unicode
libraries
があります。
string
型は単なる文字のリストと言うわけではありません。
もっと効率的な内部表現で実装されています。
unit
型は C の void
型に似ていますが、
これについてはもう少し後で述べます。
明示的/暗黙の型変換
C やその派生言語では、整数型がある特定の条件で浮動小数点型に昇格します。
たとえば、1 + 2.5
と書いた場合、
第一引数の整数は浮動小数点数に昇格して、結果は浮動小数点数になります。
((double) 1) + 2.5
と書いたのと同じ実行結果になるわけですが、
これが暗黙の内に行われます。
OCaml ではこのような暗黙の型変換は一切行われません。 OCaml で 1 + 2.5
と書くと型エラーとなります。 OCaml の +
演算子は二つの整数を引数に取るので、ここで
整数と浮動小数点数を与えられるとエラーを通知します。
# 1 + 2.5;;
Error: This expression has type float but an expression was expected of type
int
(OCaml のエラーメッセージはフランス語から英訳されたもので、 「float になっているけど int を使うべきでは?」 「あなたは浮動小数点数を使ったがここは整数型が期待されている」 くらいの意味です。訳注:日本語を母語とする我々にはどうでもいい話ですね)
二つの浮動小数点数を足し算したい場合にはこれと違う演算子 +.
を
使います (後置されているピリオドに注意)。
OCaml は自動的に int を float に昇格することはないので、次のも間違いです:
# 1 +. 2.5;;
Error: This expression has type int but an expression was expected of type
float
OCaml は第一引数に文句をつけています。
もし本当に整数と浮動小数点を足し算したい場合、どうすれば良いでしょうか
(それぞれ i
と f
という変数に格納されているとします)。 OCaml
では明示的なキャストが必要です。
(float_of_int i) +. f
float_of_int
は int
をとって float
を返す関数です。 これらの関数は
int_of_float
, char_of_int
, int_of_char
, string_of_int
など
全組合わせあり、概ね想像通りの動作をするはずです。
int
から float
への変換が特に良く出てくるので、 float_of_int
関数には短い別名が付けられており、 上の例はこのようになります。
float i +. f
(C と違い OCaml では型と関数が全く同じ名前です)
暗黙と明示的、どちらの型変換がよい?
これらの明示的なキャストが見苦しくて手間もかかると思うかも知れません。 確かに一理あるのですが、 明示的キャストを支持する少なくとも以下の議論があります。 一つめは、明示的な型変換を必須とすることによって、OCaml では型推論(後述) が実現できています。 型推論は明示的なキャストによる余分なタイピングを相殺して余りあるだけの すばらしい時間節約効果があります。 二つめは、C でデバッグの手間がかかっているのなら、 (a) 暗黙のキャストに起因するバグが探しづらいこと、 (b) 暗黙のキャストが何処でどう実行されたのか試行錯誤するのに時間がかかること、 に気づくべきです。 キャストを明示することでデバッグが楽になります。 三つめに、ある種のキャスト(特に int <-> float) は高価な変換操作である事実です。 型変換を明示しないことによる利益はないと思いますよ。
普通の関数と再帰関数
C などの言語と違って、 単に let
ではなく let rec
と明示的に書かない限り、 関数は再帰的には使えません。
再帰関数の例を示します:
# let rec range a b =
if a > b then []
else a :: range (a+1) b;;
val range : int -> int -> int list = <fun>
range
関数が自分自身を呼び出していますね。
let
と let rec
の違いは関数名のスコープだけです。 この関数 range
が単に let
で定義された場合には、 range
の呼び出しのところで、今定義しているところの range
関数
ではなく、既に存在するはずの(既に定義された) range
関数を探します。
let
と let rec
で定義された関数で性能的な違いはありません。
なので、常に let rec
を使うことにすれば C
のようなセマンティックになります。
関数の型
型推論があるので、関数の型を明示して書き下す必要はほとんどありません。
しかし、あなたの書いた関数を OCaml がどう推論したのかが表示されるので、
この文法を知っておく必要はあるでしょう。 関数 f
が引数 arg1
, arg2
,
..., argn
を取り、 rettype
型を返すとき、OCaml
コンパイラはこう表示します:
f : arg1 -> arg2 -> ... -> argn -> rettype
矢印は変に見えるかも知れません。が、 後述する「カリー化」というものを知ると何故こう表示されるのかも納得が行くでしょう。 今はとりあえずいくつかの例を示します。
関数 repeated
は文字列と数値を引数にとって文字列を返すのでした。この型は
repeated : string -> int -> string
関数 average
は浮動小数点数を二つ引数にとって浮動小数点数を返すのでした。
average : float -> float -> float
OCaml 標準のキャスト関数の int_of_char
は:
int_of_char : char -> int
もし関数が何も返さない (C や Java の void
) のなら、 unit
型を返す、と書きます。 たとえば fputc
と同等の OCaml 関数は:
output_char : out_channel -> char -> unit
多相関数
ここでちょっと変わったものを紹介します。 引数が何でも良いような関数というのはどうでしょう。
ここで引数を取るけれどそれを無視して常に3を返す変な関数があります。
let give_me_a_three x = 3
この関数の型はなんでしょう? OCaml では「使いたいどんな型でも」という意味の特別な記法があります。 単引用符(アポストロフィ)に文字、です。 普通、上の関数の型はこう表記されます。
give_me_a_three : 'a -> int
ここで 'a
はどんな型でも良いことを意味します。 たとえばこの関数を
give_me_a_three "foo"
とか give_me_a_three 2.0
とか呼び出しても、
いずれも OCaml として正しい式です。
しかしこの例では多相関数が何故有用なのかは分からないでしょう。 一般的にはとても有用なのですが、それはおいおい述べようと思います。 (ヒント: 多相関数は C++ や Java 1.5 等のテンプレートに似ています)
型推論
このチュートリアルのテーマは、 関数型言語には本当にすごい特徴がいっぱいあって、 OCaml はそれらを余すところなく詰め込んだ言語になっていること、 そして、そうだからこそ、 本物のプログラマが実戦で使っている言語になっていること、です。 しかし、これら本当にすごい特徴というものの多くは、 実は「関数型プログラミング」とは関係がなかったりします。 実際、これから最初の本当に凄い特徴を述べますが。 まだ関数型プログラミングが何故「関数型」と呼ばれるのか、 ここでもまだ言及しません。 ともかく、これが最初の本当に凄い特徴です。型推論。
一言で言うと、 あなたが書いた関数や変数の型を明示的に宣言する必要がなく、 OCaml があなたのかわりにやってくれるものです。
さらに OCaml は型の整合が取れているかどうかを (別のファイルにあるものでも) チェックしてくれます。
しかし、OCaml は実用的な言語でもあるので、 型チェックをバイパスするような型システムの抜け道を用意してあります。 よほどのハッカーでもない限りこんな抜け道を使うことはないでしょうが。
さて前に出てきた average
関数に戻りましょう。
# let average a b =
(a +. b) /. 2.0;;
val average : float -> float -> float = <fun>
摩訶不思議! OCaml は勝手にこの関数が二つの float
を引数にとって
float
を返すと判断しました。
どうしてこんなことができるのでしょう? まず、a
と b
が何処で使われてるのか、つまり (a +. b)
を見てみます。 +.
というのは常に二つの float
を引数にとる単純な関数なので、 a
も b
も float
でないといけません。
次に、/.
も float
を返す関数で、 これが average
関数の返値になるわけですから、 average
関数の返値の型もまた同じく
float
でないといけません。 したがって、まとめると average
の型は
average : float -> float -> float
型推論はこれくらい短いプログラムだととても簡単ですが、
大きなプログラムであってもちゃんと動きます。 型推論は、
他の言語で良くありがちなセグメントフォールトや NullPointerExceptions
,
ClassCastExceptions
(Perl だとせっかく実行時警告が出るのに無視したり…)
などを引き起こすようなバグを取り除くことになるので、
これこそが最も時間の節約になる特徴となっています。