OCaml 基礎最速マスター

OCamlの基礎を1時間で。

OCamlとは

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

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

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

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

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

基礎

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

$ ocaml
        Objective Caml version 3.11.1

#

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

# 1;;
- : int = 1
# "some";;
- : string = "some"
# 1.0;;
- : float = 1.
# 'a';;
- : char = 'a'

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

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

# print_int 1;;
1- : unit = ()
# print_string "some";;
some- : unit = ()
# print_float 1.0;;
1.- : unit = ()
# print_char 'a';;
a- : unit = ()
# Printf.printf "%d%s%f%c" 1 "some" 1.0 'a';;
1some1.000000a- : unit = ()

ここで、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 x = 1 in x;;
- : int = 1
# let x = 1 in
  let y = x in
  y;;
- : int = 1

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

# let x = 1;;
val x : int = 1
# let y = x;;
val y : int = 1

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

四則演算

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

# 1 + 1;;
- : int = 2
# 3 - 2;;
- : int = 1
# 2 * 4;;
- : int = 8
# 7 / 2;;
- : int = 3
# 6 mod 3;;
- : int = 0

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

# 1.0 +. 1.0;;
- : float = 2.
# 3.0 -. 1.0;;
- : float = 2.

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

# 3.0 -. (float 1);;
- : float = 2.

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

ちょっとだけ型の話

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

いくつかのデータ構造

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

(* 単方向リストは、要素を [] で囲んで ; で区切って書きます。 *)
# [1; 2; 3];;
- : int list = [1; 2; 3]
(* 先頭への追加は :: を使います *)
# 1 :: 2 :: 3 :: [];;
- : int list = [1; 2; 3]
(* @ でリストを連結できます *)
# [1; 2] @ [3];;
- : int list = [1; 2; 3]
(* リストには同じ型のものしか入りません *)
# [1; 1.0];;
Error: This expression has type float but an expression was expected of type
         int (* int が予測されているところに float が来ました *)

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

# List.hd [1; 2; 3];;
- : int = 1
# List.tl [1; 2; 3];;
- : int list = [2; 3]
(* タプルは、コンマで区切って書きます。一応括弧を付けてください *)
# (1, 3, "some");;
- : int * int * string = (1, 3, "some")
(* タプルは同じ型じゃなくてもかまいません。*)

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

# fst (1, 2);;
- : int = 1
# snd (1, 2);;
- : int = 2
(* 配列は [| |] 中に要素を入れます。これも同じ型じゃないと入りません。*)
# let x = [|1; 2; 3|];;
val x : int array = [|1; 2; 3|]
(* 要素へのアクセスは、x.(n) のようにします。なんか変な文法ですね。*)
# x.(0);;
- : int = 1
(* 配列の要素は実は更新可能です。配列そのものは更新できません。*)
# x.(0) <- 2;;
- : unit = ()
# x;;
- : int array = [|2; 2; 3|]
(* 配列の要素数は Array.length で取ります。*)
# Array.length x;;
- : int = 3

制御式

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

if 1 = 1 then 2 else 3

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

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

(* if の後が bool じゃなくて int*)
# if 0 then 1 else 2;;
Error: This expression has type int but an expression was expected of type
         bool
(* then は int なのに else は float *)
# if true then 0 else 1.0;;
Error: This expression has type float but an expression was expected of type
         int

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

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

(* 何も表示しないつもりなのに 2 が表示されてしまった *)
# if true then
    if false then print_int 1
  else
    print_int 2
  ;;
2- : unit = ()

(* 今度は OK *)
# if true then begin
    if false then print_int 1
  end else
    print_int 2
  ;;
- : unit = ()

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

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

関数

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

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

$ print_int 1;;
1- : unit = ()
# Printf.printf "%d%s%f%c" 1 "some" 1.0 'a';;
1some1.000000a- : unit = ()

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

# let f = fun x y -> x + y;;
val f : int -> int -> int = <fun>
# f 1 2;;
- : int = 3

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

# let f x y = x + y;;
val f : int -> int -> int = <fun>

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

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

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

# (fun x y -> x + y) 1 2;;
- : int = 3

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

# let apply f x y = f x y;;
val apply : ('a -> 'b -> 'c) -> 'a -> 'b -> 'c = <fun>
# let add x y = x + y;;
val add : int -> int -> int = <fun>
# apply add 1 2;;
- : int = 3

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

多相型と型推論

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

たとえば、

# let id x = x;;
val id : 'a -> 'a = <fun>

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

C++ でいうところの、

template<typename T>
T id(T x) { return x; }

みたいなもんです。

ループと再帰

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

# let rec loop i n =
    if i < n then begin print_int i; loop (i + 1) n end
    else ()
  ;;
val loop : int -> int -> unit = <fun>
# loop 0 10;;
0123456789- : unit = ()

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

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

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

# type v =
    | Int of int
    | Float of float
  ;;
type v = Int of int | Float of float

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

# Int 3;;
- : v = Int 3
# Float 3.0;;
- : v = Float 3.

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

# [Int 3; Float 3.0];;
- : v list = [Int 3; Float 3.]

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

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

# match x with
    | Int v -> print_int v
    | Float v -> print_float v
  ;;
3- : unit = ()

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

# let rec fstof3 x = match x with
    | (a, b, c) -> a
  ;;
val fstof3 : 'a * 'b * 'c -> 'a = <fun>
# let rec sum lst = match lst with
    | [] -> 0
    | x :: xs -> x + sum xs
  ;;
val sum : int list -> int = <fun>
# sum [1; 2; 3; 4; 5];;
- : int = 15
# let rec fib x = match x with
    | 0 -> 1
    | 1 -> 1
    | x -> fib (x - 1) + fib (x - 2)
  ;;
val fib : int -> int = <fun>
# fib 4;;
- : int = 5

さらに

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

2010-11-01