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

新規 編集 添付