OCaml入門(2)このページは最後に更新されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。 last mod. 2008-04-07 (月) 23:53:50
last mod. 2008-04-07 (月) 23:53:50
匿名関数とカリー化OCaml では、関数に名前を付けなくても関数を定義することが出来ます。 # fun x -> 1 + x;; - : int -> int = <fun> # fun x y -> x + y;; - : int -> int -> int = <fun> これを匿名関数と言い、通常の関数と同じ様に、適用することも出来ます。 # (fun x y -> x + y) 1 2;; - : int = 3 また、束縛も可能です。 # let f = fun x y -> x + y;; val f : int -> int -> int = <fun> # f 1 2;; - : int = 3 後に出て来ますが、関数を引数として渡す場合、簡単な関数であれば、匿名関数を直接書くことができれば便利です。 さて、fun を二引数で使う場合を考えましょう。驚くかもしれませんが、次の二式は同値となります。 # fun x y -> x + y;; - : int -> int -> int = <fun> # fun x -> fun y -> x + y;; - : int -> int -> int = <fun> 始めの式は、引数を二つ取る様に定義しました。実は、二つ目の式は、"引数を一つ取る関数を返す、引数を一つ取る関数"を定義しています。全ての関数は、この様に引数を一つとる関数によって定義することが出来ます。これをカリー化表現と言います。 部分適用C 等の手続き型言語では、関数の引数を全て与えなければいけませんでした。ところが、OCaml では、引数を一つだけ与えることが可能となります。 let f x y = x + y を考えましょう。f を例えば次の様に部分適用します。 # let f x y = x + y;; val f : int -> int -> int = <fun> # f 1;; - : int -> int = <fun> ここで、f 1 の型に注目して下さい。f は、もともと二つの引数をとる関数型だったのが、部分適用することで引数を一つ取る関数が返って来ています。 様々な型以前 int や float 等の基本的な型を扱いましたが、ここではそれらを組み合わせる為の型を紹介しましょう。 まず、list です。OCaml には、言語組み込みの単方向連結リストが存在します。 # [1; 2; 3; 4; 5];; - : int list = [1; 2; 3; 4; 5] リストは、[ ] で括り、セパレータとして ; を使います。 リストには、操作として cons と append が定義されています。cons は、要素とリストを組み合わせて、新しいリストを作る操作であり、append は、リストとリストを連結する操作です。それぞれ、::, @ を演算子として使います。 # 1 :: [2; 3; 4];; - : int list = [1; 2; 3; 4] # [1; 2; 3] @ [4; 5; 6];; - : int list = [1; 2; 3; 4; 5; 6] [1; 2; 3] を、cons を使って書くと、以下のようになります。ここで、[] は、空リストで、何も要素が入ってないリストです。 # 1 :: 2 :: 3 :: [];; - : int list = [1; 2; 3] リストには、同じ型のものしか入れる事が出来ないことに注意して下さい。また、[] は、任意の要素と cons が取れるように、型 'a list が付いています。 # 1. :: [];; - : float list = [1.] # 'a' :: [];; - : char list = ['a'] # [1; 2; 3] :: [];; - : int list list = [[1; 2; 3]] 次に、tuple (タプル) を紹介します。タプルは、任意の要素を任意個だけ、まとめて表現する為の型です。タプルは、カンマで要素を区切ることによって表現できます。直積であると思えば、分かる人には分かりやすいでしょう。 # 1, 2, 3, 4;; - : int * int * int * int = (1, 2, 3, 4) # 1, [1; 2];; - : int * int list = (1, [1; 2]) # [(1, 2); (2, 3); (6, 7)];; - : (int * int) list = [(1, 2); (2, 3); (6, 7)] カンマで区切るだけでは見にくいので、なるべく括弧でくくって下さい。 もちろんリストやタプルは、引数として渡したり、返却したりすることも出来ます。 パターンマッチングさて、リストやタプルが適当な変数に束縛されているとしましょう。ここでは、t に int list が束縛されているとします。このとき、リストの先頭要素を取り出したいのですが、それにはどうすれば良いのでしょうか。 もちろん、今までの枠組みでは対処できません。対処する為には、パターンマッチングを用いる必要があります。 # let t = [1; 2; 3; 4; 5];; val t : int list = [1; 2; 3; 4; 5] # match t with | [] -> 0 | x :: xs -> x;; - : int = 1 パターンマッチングは、match t with という構文を使い、その後に、| を入れてパターンを列挙し、そのパターンに合った場合の処理を、-> 以下に書くことで行います。尚、最初の | に限り、省略することが出来ます。 また、_ は任意のパターンとマッチします。パターンマッチは上から行われるので、最後に _ を書いてデフォルトの処理をさせることも可能です。 # let t = [1; 2; 3; 4; 5];; val t : int list = [1; 2; 3; 4; 5] # match t with [] -> 0 | x :: xs -> x;; - : int = 1 タプルのパターンマッチングも同様に行います。 # let t = (1, 2);; val t : int * int = (1, 2) # match t with (x, y) -> x + y;; - : int = 3 もし、パターンマッチングが全てのパターンをマッチしていない場合は、警告が表示されます。 # match t with x :: xs -> x;; Warning: this pattern-matching is not exhaustive. Here is an example of a value that is not matched: [] - : int = 1 この場合、[] が来た場合に対処出来ないことはお分かりでしょう。 パターンマッチングを使った関数を書く事で、色々な処理を簡単に書くことができます。次の関数は何をしているか考えてみて下さい。(分からなければ、[], [1], [1; 2], ... に f を適用させてみて下さい。) # let rec f x = match x with [] -> 0 | x :: xs -> x + (f xs);; val f : int list -> int = <fun> 同様に、次の関数に関しても考えてみて下さい。 # let rec f x = match x with [] -> [] | x :: xs -> (x * x) :: (f xs);; val f : int list -> int list = <fun> 高階関数OCaml では、関数も引数に取る事が出来ます。関数を引数に取る関数を高階関数と言います。 適当な関数 f による畳み込み演算 foldr は、次の様に定義されます。 foldr f v [a1; a2; ...; an] = f a1 (f a2 (... (f an v))) これは、OCaml では次の様に書く事が出来ます。 # let rec foldr f v lst = match lst with [] -> v | x :: xs -> f x (foldr f v xs);; val foldr : ('a -> 'b -> 'b) -> 'b -> 'a list -> 'b = <fun> これを使うと、リストの要素の和も簡単に書くことができます。 # foldr (fun x y -> x + y) 0 [1; 2; 3; 4; 5; 6; 7; 8; 9; 10];; - : int = 55 |