OCaml入門2 OCamlでは他の一般的な言語より多数の型が用意されている。これを 整理してみたい。 まずは基本のデータ型; int: -2^30 から +2^30までの整数。つまり-1073741824から1073741823の整数。64bitの環境では -2^62から2^62-1までの整数。 float: Cでいうdouble型と同じ。IEEE754に従っている。 char: 文字。8bitの整数。つまり0から255までの整数。現在の実装では0から127までの数は ASCII標準に従い、128から255はISO 8859-1 に従う。 string: 文字列。現在の実装では2^24-5の文字数(つまり16777211)の文字列をサポートしている。 64bitの環境では2^57-9個の文字列をサポートしている。 インタプリタを起動してこれらの基本的な操作を試してみよう。 まずint型。 これらは当然四則演算ができる。 v1+2;; 2-1;; 1*2;; 3/2;; ちなみに自分のマシーンがサポートしている最大のint型は max_int;; 最小のint型は min_int;; で調べられる。 int型を表示するのは print_int 1;; などとするとint型が表示できる。 他にも ~-1;; succ 3;; pred 2;; 上から順にー1をかける。1を足す。1を引く。となっている。 剰余や絶対値を求める関数も定義されている。 abs (-2);; 7 mod 3;; この2つには少し注意してほしい。まずmodは四則演算のようにmodを真ん中に書く。 あとabsは関数であるのだが abs -3;; と書くとエラーになる。このかっこの忘れはOCamlのプログラムでのエラーのけっこうな 量を占めている気がする。これを避けるには abs ~-4;; abs (-4);; のどちらかにする。 float型: 最初必ずはまるのはOCamlではfloat型の四則演算はint型とは異なるということだ。 1.2 +. 2.2;; 3.2 -. 3.2;; 4.0 *. 2.1;; 4.5 /. 1.2;; 2.0 ** 3.0;; またint型と同様に ~-.4.2;; abs_float ~-.3.5;; mod_float 4.0 3.2;; 一番上が単項演算子のマイナス。-.4などとしても同様なのだが、字句解析などで エラーを起こしやすいので~-.とやっといたほうがいい気がする。例えば次はエラーを 起こす。 abs_float -.3.5;; これなら正しい。 abs_float (-.3.5);; 言うまでもないと思うが下2つはそれぞれ絶対値と剰余を求める。またおもしろいところでは OCamlではfloat型にinfinity(無限大)が定義されている。 infinity;; neg_infinity;; 他にも epison_floatやnanなどが特別な値として定義されている。詳しくはManual Chapter 19 を読んでほしい。 キャラクター型はシングルクオテーションで表す。 'a';; 'B';; など。基本的な関数として int_of_char 'a';; char_of_int 102;; などがある。 文字列型はダブルクオテーションで表す。 "abcd";; "erte";; 基本的な操作として連結がある。 "abc" ^ "def";; ちなみにOCamlでは文字列は変更可能だ。例として次のように打ち込んで みてほしい。 let a = "abc";; a.[1] <- 'l';; a;; aの内容が変更されているのが確認できたと思う。あとこのように文字列から文字を取り出すには "abcd".[2];; などとする。 文字列の中でダブルクオテーションなどを使いたいときは\を組み合わせて使う。 \' -> ' \" -> " \\ -> \ 右側が文字列の中で本来使いたい文字、左側が文字列中での実際の表記法。 もう使っているが変数は let a = 2;; などと定義する。関数もletで定義する。 let func1 a = a + 1;; let func2 () = print_endline "a";; print_endline は文字列を表示するという点ではprint_stringと同じだが最後に 改行を加える。 再帰関数はlet rec を使う。 let rec func3 a = if a=0 then 1 else a + func3 (a-1);; 呼び出すには func1 3;; func2 ();; func3 4;; などとする。 OCamlでは比較演算子もCなどとは違う。またif文などもいくつか気をつけるべきところがある。 こういった基本的な文法を見ていきたい。 例えば次のように打ってみてほしい。 let a = 1;; let b = 1;; if a==b then print_endline "true" else print_endline "false";; trueと表示されたはずだ。では let c = "a";; let d = "a";; if c==d then print_endline "true" else print_endline "false";; とやってみるとこんどはfalseと表示される。これは==が物理的等価 (physical equality)で判断するためだ。次のようにすると両方とも trueと表示される。 let a = 1;; let b = 1;; if a=b then print_endline "true" else print_endline "false";; let c = "a";; let d = "a";; if c=d then print_endline "true" else print_endline "false";; つまりmutable(変更可能)で定義されたものは物理的等価と構造的等価 が異なるので==と=の結果が異なるものになる。mutableというのが良く 分からない人はとりあえず==は使わないと覚えていても問題ないと思う。 =の否定は<>。==の否定は!=になる。 これらの比較演算子を試してみよう。次のような関数を用意する。 let func1 a b c = if a b c then print_endline "true" else print_endline "false";; 演算子も結局は関数なので次のように関数の引数にできる。 func1 (>) 2 3;; func1 (>) 3 2;; func1 (>=) 4 2;; func1 (>=) 2 4;; func1 (>) 2 3;; func1 (<) 2 3;; ちなみに関数を調べるときは例えば func1;; とするととる型を調べられる。上のようにやると - : ('a -> 'b -> bool) -> 'a -> 'b -> unit = のように表示されたと思う。これはまだ決まっていない型'a , 'b からbool値を返す関数と 'aと'bという型をとる関数という意味だ。この場合'aと'bは基本的に何でも良い。 今の例ではたまたまint,intだったが let func2 a b = if (int_of_float a) >= b then true else false;; とすれば func1 func2 2.3 2;; としても問題なく動く。 次にif文でのポイントだがthen節とelse節は同じ型を返さなくてはいけない。つまり 次の文はエラー if 1=2 then 1 else ();; 次のようにすればエラーは消える。 if 1=2 then ignore(1) else ();; またthen節のあとにセミコロンを付けると違う意味になるのでこれも注意が必要。つまり セミコロンを書くとelse節はないものとみなす。つまり、 let func3 a b = if a=b then print_endline "a=b"; print_endline "return";; let func4 a b = if a=b then print_endline "a=b"; else print_endline "return";; func3は正しいが、func4はエラーになる。ではif文の中で複数の命令を実行したいときは どうするかといえばbegin命令を使う。これはいくつかの命令を1つにまとめる命令だ。 次のように使う。 let func5 a b = if a=b then begin print_endline "1"; print_endline "2"; print_endline "3"; end else print_endline "false";; begin節の中に限ったことではないのだが、begin節の中では返り値を返すもの が途中にあると警告が出ることが多い。これはignoreで回避する。 let func6 a b = if a=b then begin print_endline "1"; a + b; print_endline "3"; end else print_endline "false";; let func7 a b = if a=b then begin print_endline "1"; ignore(a + b); print_endline "3"; end else print_endline "false";; なおif expr1 then expr2; などとするとelse節は省略できるがこれは else () を自動的に 補っているためだ。だからelse節を省略したときはif節はunit型である必要がある。 if 1=2 then 1;; if 1=2 then ();; 一つ目はエラーで二つ目はきちんと動く。 次はforループだ。これはCなどと比べても単純なものになっている。例えば 次のように使う。 for i=1 to 10 do print_int i;print_newline();done;; for i=10 downto 0 do print_int i;print_newline();done;; この例で分かるように初期設定された値は一ずつ増えるか減るかの2種類で 増分の指定などはできない。増分を指定したループなどをしたいときは、 すこし汚くなるが、while文を使う。また以外なことにOCamlにはbreak文がない。 これは例外処理を利用する。OCamlでは例外処理を、Javaなどとは違い普通の処理の 流れを制御する目的で使う。これは例外処理の説明のところで例を挙げる。 まず簡単なwhileループの使い方から。 let r = ref 0 in while !r<10 do print_int !r;print_newline();r := !r + 1; done;; ここで使われているrefというのは参照型と呼ばれるものだ。OCamlは基本的に変数に対して 破壊的代入はできない。つまり、Cなどで普通に使う次のようなものはOCamlではエラーと なる。 let a = a + 1;; a = a + 1;; これは二つの値を比べて等しいかを返すだけで代入はできない。このような操作をするために OCamlは特別なデータ型を用意している。次のように使う。 let a = ref 0;; 値を取り出すには !a;; 代入するには a := 2;; などとする。上のように入力したあと !a;; とすると値が変更されていることが確かめられる。 そこで再びさっきの例に戻る。これはローカル変数rをint型の参照として定義して whileループの最後でインクリメントしている。 let r = ref 0 in while !r<10 do print_int !r;print_newline();r := !r + 1; done;; このようにすると増分が指定できる。break文の代わりに例外を使う処理はあとでふれる。 let in の構文が出てきたところで少しlet の使い方についてまとめてみたい。 まず一番基本は let a = 2;; などとするとグローバル変数が定義できる。一時に複数の変数を定義する方法もある。 let a = 2 and b = 3 and c = 4;; ローカル変数を定義するには let local = 3 in local * local;; local;; localはin内でのみ有効だ。letはネストすることができる。 let a = 3 in let b = 2 in a + b;; またlet and の文は同時に行われるので次の文はエラー。 let aa = 2 and bb = aa + 2 in bb;; このようにしたいときは let aa = 2 in let bb = aa + 2 in bb;; とする。 これら2つを組み合わせることもできる。 let a = 4 and b = 3 in a*b;; letで関数も定義できるわけだが、そこらへんを整理するにはfunction などから始めるのが良いだろう。 function x -> x * x などとして使う。functionは一つの引数をとってそれについての関数を返す。 (function x -> x + 1) 2;; などとすると簡単な匿名関数として使える。がこれはひとつの引数しか使えない上、 キーワードが長いのでほとんど使われない。普通funを使う。functionで複数の引数を 使いたいときは (funcion x -> function y -> x * y) 2 3;; などとする。ここらへんはOCamlというか関数型言語の特徴っぽいところなのだが、 ここではこれ以上触れない。興味のある人はDeveloping applications with Objective Caml のChapter 2 を参照してほしい。 funは複数の引数をとる。次の2つの表現は等価だ。 fun p1 p2 p3 ... pn -> expr function p1 -> function p2 -> .. -> function pn -> expr さっきのfunctionの2変数の例はfunを使ってつぎのように書ける。 (fun x y -> x * y) 2 3;; ここでletによる関数定義の仕方に戻る。つまり let func1 () = print_endline "func";; let func2 a = a + 1;; let func3 a b = a + b;; などはそれぞれ let func1 = function () -> print_endline "func";; let func2 = function a -> a + 1;; let func3 = function a -> function b -> a + b;; などと等価になる。さらに3番目の例は let func3 = fun a b -> a + b;; などとも等価になる。一番最初の例はこれらのシンタックスシュガーに なっている。つまり、プログラムを書くときにはlet の最初の引数の次の トークンが()か他の文字のときletはそれらを関数と定義する、と考えると 簡単だ(実際にはOCamlでは関数と関数以外がCほど離れてはいないのだが)。 ちなみに以前も以前も書いたが再帰関数を定義するにはlet recを使う。 関数定義はここで挙げた単純なものだけでなく型制限、ラベルづけなども できるが、後にしてとりあえず先に進む。 bool値: OCamlはJavaのように真偽を表すために特別の型が定義されていて、いくつかの 演算子が利用できる。これをbool型といい、trueとfalseの二つの値が定義されている。 true;; false;; これらの演算は次の5つ。 not true;; true && flase;; true & false;; true or false;; true || false;; notは否定、&&と&はand、orと||はorを表すが、マニュアルによると&とorは使うべきではないようだ。 配列、リスト、タプル: まずリストの例は [1;2;2];; などだ。リストはすべての型が等しいことを要求する。つまり次のはエラー。 [1;2;3.2];; リストに要素を付け加えるのは 1::[1;2;3];; などとする。リスト同士を結合するには [1;2;2] @ [1;2];; 配列の例は [|1;2;2|];; などだ。リストと違い変更可能になっている。 let a = [|1;2;2|];; a.(1) <- 9;; a;; リストと同じで配列内はすべて同じ型でなくてはいけない。上の例でも 挙げたが、要素を取り出すには a.(2);; a.(1);; などとする。また要素を変更するには a.(2) <- 3;; a.(1) <- 10;; などとする。 タプル: リスト、配列と違い異なる型をとれる。次のように定義する。 (1,1.2,'a');; これは複数の値をまとめて取り扱うのに便利だが、schemeのリストのように 使えるわけではない。タプルの値を簡単に取り出すには let a,b,c = (1,1.2,'a') などとする。またタプルの要素が2つのときには fst (1,2.3);; snd (1,2.3);; という最初と2つめのタプルを取り出す関数が用意されている。 これより複雑な操作をしたいときは後述のパターンマッチを利用する。 レコード型、バリアント型 OCamlではユーザーが独自のデータ型を定義できる。これにはtypeというキーワード を使う。 type type_ex1 = float * float;; これであらたな型が定義できたわけだが、次のようにしても、float*floatのタプルとして しか表示されない。 (1.2,2.3);; これはOCamlが最も一般的な型を表示するためで、次のように明示的に 型を指定してやれば型が定義できていることを確認できる。 ((1.2,2.3):type_ex1);; このような使い方は実際のプログラムではあまりしないと思うので、実際に使う機会の多い レコード型とバリアント型を説明する。レコード型は複数の値をもつデータ型で例えば 次のように定義する。 type complex = { re:int;im:int};; let add_complex a b = { re = a.re + b.re; im= a.im + b.im };; add_complex {re=1;im=2} {re=3;im=4};; この例のようにレコード型は type name = { name1:t1; name2:t2; ... namen:tn };; で定義して、使うには { name1:expr1 name2:expr2; ... namen:exprn };; などとする。いくつか例を挙げる。 type ratio = { num:int; denum:int };; let add_ratio r1 r2 = { num = r1.num * r2.denum + r2.num * r1.denum; denum = r1.denum * r2.denum };; add_ratio {num=1;denum=4} {num=5;denum=8};; type vector = { x:float; y:float };; let norm a = sqrt(a.x**2.0 +. a.y**2.0);; norm {x=2.3;y=4.6};; またレコード型は一度値が決まったら変更できない。つまり上のcomplex型を例として使うと let a = { re=1; im=4 };; この値は参照することは出来ても変更することはできない。これを変更できるようにするには complex型の定義を次のように変更しなくてはいけない。 type complex2 = { mutable re2:int; mutable im2:int } 次のように使う。 let a = { re2=2; im2=4 };; a.re2 <- 9;; a;; なおこの場合complex2として次のようにすると type complex2 = { mutable re:int; mutable im:int };; とすると以前の定義をつぶしてしまう。 この以前の定義をつぶしてしまう操作はインタプリタで#useを使ったり、まとめて 関数群を張り付けたりしたとき問題が起こすことが多い。次の例を見てほしい。 type complex = { re:int; im:int };; let add_complex a b = { re=a.re+b.re; im=a.im+b.im };; let a = {re=2;im=4};; type complex = { re:int; im:int };; let b = {re=3;im=7};; add_complex a b;; これは最後の場所でエラーを起こす。最初のcomplexと2つめのcomplexが違う型だと インタプリタが判断するためだ。この例のように This expression has type complex but is here used with type complex のような一見すると良く分からないエラーメッセージが出たら定義を変なところで更新して いないかを疑ってみるべきだ。 バリアント型はいくつかの値のどれかをとることができる。次のように宣言する。 type sign = Positive | Negative ここでのPositiveとNegativeはコンストラクタといい、必ず大文字で始めなくては いけない。次のようにコンストラクタを通じてバリアント型は使う。 Positive;; Negative;; let func a = if a>=0 then Positive else Negative;; func 4;; func ~-4;; コンストラクタに値を持たせることでバリアント型に値を持たせることも出来る。 type number = Int of int | Float of float | Error;; これはIntというコンストラクタはintをひとつとり、Floatというコンストラクタはfloatを ひとつとるという意味だ。引数を使って呼び出すには次のようにする。 Int(2);; Float(4.32);; Error;; 間違った引数を与えるとエラーになる。 Int(2.43) Float(3);; Error(2);; 上のようにコンストラクタの後にofを付けて型を指定するわけだが、ofのあとは 上のような単純なものだけでなくいろんなものがこれる。 複数の値のタプル: type sample1 = Sample1 of int * float * char;; 関数: type sample2 = Sample2 of (int -> int);; 再帰的定義 type sample3 = Sample3 of int * int * sample3 | Nil;; これらの中の数値などを利用したいときはパターンマッチを利用する。 なおバリアント型では基本的に値を変更できない。例えば次の例ではaの値は変更できない。 type sample = Int of int;; let a = Int(4);; 例外処理: OCamlは例外処理をサポートしている。前に述べたようにJavaなどと違って普通の処理にも 例外処理を利用する。 例外はexnという型を持っている。exception NameとすることでNameという例外を追加できる。 これはバリアント型の特殊な形と言うことができる。バリアント型と同じように値を持たせること ができる。 exception EXN1;; EXN1;; exception EXN2 of int;; EXN2(2);; これはバリアント型の特殊な値なのでコンストラクタは大文字で始めなくてはいけない。つまり 次はエラー。 exception exn1;; 例外を起こすにはraiseという関数を使う。 raise EXN1;; raise (EXN2(2));; 2番目の例は raise (EXN2 2);; と書いても同じだ。 例外を捕まえるにはtry - with という構文を使う。 exception EXN1;; let func1 () = try print_endline "a"; raise EXN1; print_endline "b"; with EXN1 -> print_endline "c";; func1 ();; while文から抜ける場合にも例えば let a = ref 0 in try while true do print_int !a;print_newline();a := !a + 1; if !a>10 then raise EXN1; done; with EXN1 -> print_endline "break";; のように使える。またtryとwithの間には関数を含めることができる。次の例 を見てほしい。 let func1 a = while true do print_int !a;print_newline();a:=!a+1; if !a>10 then raise EXN1; done;; let func2 () = try func1 (ref 0) with EXN1 -> print_endline "break";; このように定義して func2 ();; と打ち込んでみるとさっきの例と同じに例外が捕まえられていることが 分かる。 パターンマッチ: OCamlの基本的な文法はこのパターンマッチで最後になる。バリアント型などを利用したり するのには不可欠な操作でもある。 パターンマッチの基本的な構文は次の通り。 match expr with | p1 -> expr1 | p2 -> expr2 ... | pn -> exprn exprがp1のときexpr1が実行されp2のときexpr2が実行される。また 最初の|は省略できる。 match 1 with | 1 -> print_endline "a" | _ -> print_endline "b";; match 1 with 1 -> print_endline "a" | _ -> print_endline "b";; _はワイルドカードでどんなものにもマッチする。 パターンマッチは関数と一緒に使うときには省略記法がある。 function | p1 -> expr1 | p2 -> expr2 | p3 -> expr3 ... | pn -> exprn この例は let func1 = function | 1 -> print_endline "a" | _ -> print_endline "b";; func1 2;; func1 1;; などと使うのだが、個人的には非常に分かりにくいと思う。このように宣言したとき、func1は int->unitの型を持ち、引数をひとつ持つ。が定義の中身でこの引数は出てこない。引数の名前を設定しな くても良いのでなれれば便利そうだが最初のうちはわけが分からない。これは次のように書いても同じ。 let func2 = function a -> match a with 1 -> print_endline "a" |_ -> print_endline "b";; func2 1;; func2 2;; これもlet func2 までみると引数をとらないように見えて気持悪い。次のように書くのが一番見やすい と思うが、ここら辺は好みの問題なので好きなものを選んでほしい。 let func3 a = match a with 1 -> print_endline "a" |_ -> print_endline "b";; func3 1;; func3 2;; パターンにマッチしたときの処理を複数にしたいときは;を使う。 let func3 a = match a with 1 -> print_endline "a"; print_endline "a"; |_ -> print_endline "b"; print_endline "b";; func3 1;; func3 2;; 複数の処理を記述したときの最後のセミコロンはなくても良い。次の例を 正しく動く。 let func3 a = match a with 1 -> print_endline "a"; print_endline "a" |_ -> print_endline "b"; print_endline "b";; func3 1;; func3 2;; またパターンマッチはネストすることもできる。このとき処理の範囲を処理の範囲を 明確にするためbeginを使う。 let func4 a b = match a with 1 -> begin match b with 1 -> print_endline "1-1" |_ -> print_endline "1-a" end |_ -> begin match b with 1 -> print_endline "a-1" |_ -> print_endline "a-a" end;; func4 1 1;; func4 1 2;; func4 2 1;; func4 2 2;; beginはかっこでも代用可。つまり次の例は上の例と等価。 let func4 a b = match a with 1 -> (match b with 1 -> print_endline "1-1" |_ -> print_endline "1-a") |_ -> (match b with 1 -> print_endline "a-1" |_ -> print_endline "a-a");; func4 1 1;; func4 1 2;; func4 2 1;; func4 2 2;; 次にパターンマッチで使うキーワードasとwhenの使い方について説明する。それほど気にすることも ないと思うが存在くらいは知っていた方が良いだろう。asは次のように使う。 let func5 a b = match (a,b) with ( ((c,d) as arg1), ((e,f) as arg2) ) -> if (c+d) > (e+f) then arg1 else arg2;; この例だと引数のint型2つのタプルにそれぞれ名前をつけて、それを処理部分で使っている。この例だと いまいち利点が分からないかも知れないが、バリアント型を使ったりするパターンマッチではコンストラクタ 名をいちいち繰り返すのは見にくいので便利なこともある。 次にwhenの使い方を見てみよう。これはある条件を付加できるもので2重の条件分岐を行いたいときに 使う。 let func6 a b = match a with 1 when b=1 -> print_endline "1-1" |1 -> print_endline "1-a" |_ when b=1 -> print_endline "a-1" |_ -> print_endline "a-a";; func6 1 1;; func6 1 2;; func6 2 1;; func6 2 2;; この例は結局以前のパターンマッチの例と同じ処理を行う。aが1の時は、最初の条件に合致するが、 when cond のcond が真でないと最初の処理は実行されない。 パターンマッチの例として最後にリストのパターンマッチにappend、バリアント型のパターンマッチに 2分木探索を取り上げてみよう。 リストのパターマッチ let rec append_trace l = match l with [] -> print_newline () |a::b -> Printf.printf "%d," a;append_trace b;; let rec append l1 l2 = append_trace l1; match l1 with [] -> l2 |x::rest -> x::(append rest l2);; append [1;2;3] [2;3];; append [21;23;12] [4;5;6];; l1が小さくなって様子が確認できる。そのあとに徐々に足されていく。 バリアントのパターンマッチ type 'a btree = Empty | Node of 'a * 'a btree * 'a btree;; let rec member x btree = match btree with Empty -> false | Node(y,left,right) -> if x=y then true else if x Node(x,Empty,Empty) | Node(y,left,right) -> if x<=y then Node(y,insert x left,right) else Node(y,left,insert x right);; 簡単に解説すればinsertは条件を満たすように木を下り、空のところまで きたら自分の値を記憶させる。これを試すのに次のような関数を用意する。 let rec func7 l = match l with [] -> Empty |hd::tl -> insert hd (func7 tl);; let a = func7 [1;2;3;4;5;6;7];; member 2 a;; member 4 a;; member 10 a;; member 100 a;;