OCaml 基礎最速マスター

OCamlの基礎を1時間で。

OCamlとは

OCaml は Haskell とは違って純粋でない関数型言語です。ML (Meta Language) という言語ファミリーの方言の一つで、フランスの INRIA という研究所で開発されています。速度を稼ぐために命令型のように書こうと思えば書けるし、遅延評価もデフォルトではしません。その分、実用的なアプリケーションが書きやすくなっています。

他の言語をある程度知っている人は、これを読めば OCaml のとりあえずの基礎をマスターして OCaml を書くことができるようになります。他の関数型言語の知識は仮定していませんが、C/C++ ぐらいの知識があれば読めると思います。元の Perl 基礎文法最速マスターではリファレンスぽい作りですが、チュートリアルぽくなってしまいました。

なお、読んでいると分かりますが、色々とめんどくさいことが多いように感じます。しかし、これをちゃんと書くと、コンパイルに通るだけでかなりの数のバグがつぶれてくれているという恩恵を受けられます。我慢して使うといいことがあります。はじめは我慢してみてください。

この文章には不足やバグがいっぱいあると思いますので是非指摘してください。

他の最速マスター系は はてな的プログラミング言語人気ランキング あたりを見てください。

基礎

OCaml はコンパイルして実行することも対話型インタプリタで実行することもできますが、ここでは対話型インタプリタで実行します。対話型インタプリタは ocaml コマンドで起動します。

とりあえず、適当に値や式を打ち込み、;; を入力するとそのまま評価されて出力されます。

ごらんの通り、型と値が表示されます。1.0 は float 型になっていますが、OCaml の float は C の double と同じです。C の float に相当する型は、標準にはありません。

print 系で表示をするには、print_int などのように型を付けるか、Printf.printf を使います。

ここで、1- : unit = () の様に表示されているのは、1print_int によって表示された部分で、- : unit = () は OCaml のインタプリタによって表示された部分です。OCaml では全ての式は値を持ちますが、特に意味のない値として、() という値が定義されています。これは unit という型を持ちますが、void みたいなものだと思ってしばらくはかまいません。

なお、コメントは、(* コメント *) のように (* *) でくくります。コメントはネストができるので、(* (* some *) *) も正当なコメントです。

変数

OCaml の変数は普通の言語の変数とはちょっと違って、「値に名前を付けたもの」です。普通の言語の変数は「値を入れる箱」の名前であり、その中身をいくらでも変更ができますが、OCaml は「値に名前を付けたもの」なので、中身を変更することはできません。ただ、同じ名前を再び付けてしまって実質書き換えたようにみせることはできます。C でいうところの const が付いていると思ってもらえれば、しばらくは OK です。

OCaml の変数は let 式で定義します。let 式は let 変数名 = 式1 in 式2 のように使います。「式1」の値の名前を「変数名」で名付け、「式2」中で参照できるようにします。

なお、トップレベル (要するに、他の式の中ではない)で let 式を書く場合は、in 以下を省略して、グローバル変数的な扱いにすることができます。

この場合、式の値を出力した部分に変数名が付いていることが確認できます。(val x の部分)

四則演算

四則演算は +, -, *, / が普通に使えます。int に限っては。あまりの演算は mod です。

float 型の値では、各演算子の後に . を付けなければなりません。めんどいです。すいませんすいません。

しかも float から int を引こうとすると、自分で intfloat に変換しなければなりません。これには float 関数が使えます。本当にめんどくさいですね。すいませんすいません。これには訳があるのです。それは次の章で。

また、変数の章で説明しましたが、変数は一度値を入れると変更できないので、+= に相当するような演算子はありません。なくはないのですが、邪道なので。

ちょっとだけ型の話

どうして ++. が分かれているのかというと、OCaml が型に対して非常に厳密だからです。+intint の足し算と定義され、また+.floatfloat の足し算と定義されています。int と float の和というような定義の足し算は自分で作らない限りありません。OCaml は(一部の例外を除いて)勝手に型変換をしないので、intfloat にするのは自分で変換する必要があります。

いくつかのデータ構造

組み込みで単方向リスト、タプル、配列があります。レコードもありますがしばらくは使わなくてまあなんとかなるでしょう。単方向リストやタプルは非常に良く使うので重要です。

List.hd で 先頭要素、List.tl で先頭要素を除いたリストをとれます。

2つ組の場合は、fst, snd でそれぞれ1つめの要素、2つめの要素をとれます。3つ組み以上の場合は fst, snd は使えません。この辺は後のパターンマッチの章で出てきます。

制御式

if は通常 if-then-else で書きます。

値の比較は = です。== だとポインタで比較になります。デフォルトでは = で OK。値の否定の比較は<> で、ポインタでの否定の比較は != です。紛らわしいですね。いや本当に。あとの <, >, <=, >= は一緒です。

また if-then-else も型に厳密で、if の後は bool 値のみです。また、thenelse の後は両方とも同じ型でなければなりません。なので、次のようなのはだめです。

else 以降は省略できますが、省略すると else () があるとみなされます。()unit 型の値でした。つまり、thenunit しかとれなくなります。

if をネストするとき、C の { } 相当のものを入れないと、内側の ifelse がぶらさがってしまう問題があります。C の { } 相当のものは begin end です。

この後オリジナルでは文字列の操作などが続きますが、OCaml はそういうのはあまり得意でないのでばっさり省きます。必要なら String 系のモジュールのマニュアルを見てください。

なお、whilefor もあるのですが、邪道なのでここでは触れません。ではループどうするのか? という問いは後の章で説明されます。

関数

やっと関数型言語ぽい説明をするところまで来ました。

関数適用に関しては、実はすでに使っています。関数 引数 引数 ... というように、空白で区切って連続して書けば関数適用になります。

OCaml は関数型言語なので、関数も値です。従って名前を付けて変数に入れることができます。関数は fun 引数 引数 ... -> 式 の様に定義します。関数の返値は最後に評価した式の値です。なお、return はありません。

毎回 fun を書くのはめんどくさいですね。そういう場合の略記法もあります。

ここで、関数の型に注目すると、fint -> int-> int という型になっています。これは、int を2つ引数に取って(最後の) int を返す関数、と実戦的には読みます。本当は違いますけどね! 知りたい人は「カリー化」という単語でググってみてください。

さらに! 関数の定義を良くみると、どこにも int と書いてないのに勝手に int を2つ取って int を返す関数になってますね? これは、+intint の足し算である、と決めたせいです。正しく足し算するためには + の左辺と右辺は int なので、xyint じゃなければなりません。また、int + intint なので、f 自体も int を返すはずです。というわけで、fint を2つとって int を返す関数でなければならないのです。このように、勝手に型を決めてくれる機能を、型推論と言います。これのおかげでバグが非常に減ります。ありがたやありがたや。たまにうざいけどね!

なお、関数は別に変数に束縛する (という単語は初めて出てきましたが、値に名前を付けるという意味です) 必要なく使えます。

また、関数の引数としても渡せます。

おっと、'a とか 'b とか 'c とかいう型はなんでしょう。これは多相型というものです。

多相型と型推論

型推論が行われた際に、完全に型を決めきれないと多相型というものが現れます。 上の apply なんかがそうで、'a とか 'b とかいう型が出てきています。非常におおざっぱにかつ乱暴に言うと、OCaml の関数はデフォルトでもう C++ のテンプレート関数みたいなもんだと思ってください。

たとえば、

は、x がどういう型であれ自分自身を返します。

C++ でいうところの、

みたいなもんです。

ループと再帰

関数中で自分自身を呼び出すこと(再帰呼出)で、ループをさせることができます。C 的には気持ち悪いですしスタックも使いそうですが、大丈夫。関数の最後で自分を呼び出す限りは、最適化が効いてループと同じコードになります。これは保証されています。なので、ループしようと思ったら関数書いて再帰します。なお、自分自分を呼び出すときは let じゃなくて let rec を使わなければなりません。そういうもんだと思ってください。letlet rec の違いは、式の中で自分自身を参照できるかどうかが違います。

言い忘れてましたが、; を使ってで式を2つ以上書けます。C と同じで begin-end で囲まないと ;if が終わるので注意。

データタイプとパターンマッチ

OCaml ではデータ型を定義することができます。データ型とは、C/C++ の enumunion を併せて、強力でかつ安全にしたものです。データ型は type で定義します。

大文字から始まる IntFloat の部分をタグとか呼んでいます。タグは大文字で始まらなければなりません。一度このようにデータ型を作ると、

のようににして値を作ることができます。型は両方とも v になっているのに注意。つまり同じ型なので、リストなどに入れられます。

データ型を一度作った後、中のデータを参照したいときはパターンマッチが使えます。これは非常に強力です。どうして他の言語にこれがないんでしょうか?

パターンマッチは match 式で下のように書きます。-> の前にパターンを書き、そこに変数名を書いておくと、パターンに合うように変数に値が束縛されます。

パターンマッチはデータ型以外でも使えます。タプルとかリストとか。また、C の switch の代わりにもなります。

さらに

OCaml はもっといろんな文法、機能がありますが、ここでは紹介しきれません。ocaml.jp には日本語の情報がもっとありますから是非そちらも見てくださいね!

2010-11-01