第 4 章 高階関数,多相性,多相的関数 41
4.1.3 カリー化と関数を返す関数
Objective Caml の関数は全て一引数であるため,引数が二つ以上必要な関数を定義するには組を
用いることをみてきた.ここでは,「関数を返す関数」を使って引数が複数ある関数を模倣できる方 法をみる.このような「関数を返す関数」を有名な論理学者Haskell Curryの名をとってカリー化関 数(curried function)と呼ぶ2.
基本的なアイデアは「x とy を受け取りeを計算する関数」を「xを受け取ると,『y を受け取って e を計算する関数』を返す関数」として表現することである.具体的な例として,二つの文字列s1, s2 から s1s2 のような連結をした文字列を返す関数を考えてみよう.これまでにみてきた,組を使っ た定義では,
# let concat (s1, s2) = s1 ^ s2 ;;
val concat : string * string -> string = <fun>
と定義され,型はまさに「文字列を二つ(組にして)受け取り文字列を返す」ことを表している.使う 場合も二つの文字列を同時に指定してconcat ("abc", "def")のように呼び出す.
さて,この関数を カリー化関数として定義してみよう.
# let concat_curry s1 = fun s2 -> s1 ^ s2;;
val concat_curry : string -> string -> string = <fun>
concat_curryはfun式を用いて,「s2を受け取って(既に受け取り済の)s1と連結するような関数」
を返している.concat_curryの型はstring -> (string -> string)と同じで,そのことを示し ている.この関数を呼び出すには,2回の関数適用を経て,
# (concat_curry "abc") "def";;
- : string = "abcdef"
のように行う.(...)内の関数適用で,「”abc” と与えられた引数を連結するような関数」が返ってき ており,外側の関数適用 (...) "def" で文字列の連結が行われる.
カリー化関数は,組を用いた定義と違って,最初のいくつかのの引数を固定したような関数を作り たい時に簡潔に実現できる.例えば,敬称 Mr.を名前(文字列)に付加する関数を
# let add_title = concat_curry "Mr. ";;
val add_title : string -> string = <fun>
# add_title "Igarashi";;
- : string = "Mr. Igarashi"
と定義することができる.add_titleは引数の置き換えモデルにしたがって,fun s2 -> "Mr. " ^ s2 という関数に束縛されていると考えることができる.このように,カリー化された関数の一部の引数 を与えて,特化した関数を作ることを部分適用(partial application)と呼ぶ.
カリー化関数の型は,読み方によって,「二引数の関数の型」と読むことも,「関数を返す関数」と読 むこともできる.
2この考えはSch¨onfinkelという人が発見したらしいがなぜがCurry化と呼ばれている.
関数定義の文法拡張 上のカリー化関数の定義方法を,fun を入れ子にすることによって,三引数,
四引数の関数の表現に拡張していくことも可能である.
実は,Objective Camlでは,funを入れ子にする代りに,letやfunでのパラメータパターンを 複数個並べることによって,カリー化関数をより簡潔に定義することができる.先の例は,
# let concat_curry s1 s2 = s1 ^ s2;;
val concat_curry : string -> string -> string = <fun>
と定義することも,
# let concat_curry = fun s1 s2 -> s1 ^ s2;;
val concat_curry : string -> string -> string = <fun>
と定義することも可能である.一般的には,
fun hパターン1i -> fun hパターン2i -> ... fun hパターンni -> e は
fun hパターン1i hパターン2i ... hパターンni -> e
と同じである.(letについても同様にパターンを空白で区切って並べることができる.)
また,関数適用も (((f x) y) z) と書く代りに f x y z と,括弧を省略することができる.
(別の言い方をすると,関数適用式は左結合する.) 関数型構築子は,既に見たように,右結合し,
t1 -> t2 -> t3 -> t4 はt1 -> (t2 -> (t3 -> t4))を意味する.
中置/前置演算子 これまで,なんの説明もなしに使ってきたが,+,^などの中置演算子(infix operator) は,内部ではカリー化された関数(int->int->intなどの型をもつ)として定義されている.さらに,
プログラマが新たな中置演算子を定義したり,(勧められないが)+などを再宣言することすらも可能 である.
中置演算子は (と)で囲むことによって,通常の関数として(前置記法)で使うことができる.
# (+);;
- : int -> int -> int = <fun>
# ( * ) 2 3;;
- : int = 6
*の前後に空白が入っているのは,コメントの開始/終了と区別するためである.
中置演算子として使用可能な記号は,mod, lor, or などの前章までに中置演算子として紹介した キーワード,もしくは
• 1文字目が=,<,>,@,^,|,&, +,-,*,/,$,%のいずれかで,
• 2文字目以降が !,$,%,*,+,-,.,/,:,<,=,>,?,@,^,|,~のいずれか を満たす文字列である.
定義をするには (h中置演算子i)を普通の名前だと思って行う.
# let (^-^) x y = x * 2 + y * 3;;
val ( ^-^ ) : int -> int -> int = <fun>
# 9 ^-^ 6;;
- : int = 36
4.1. 高階関数 45
また,前置演算子といって,定義するときや単独で関数値として使うときは() が必要な,記号列 からなる名前が用意されている.
# let ( !! ) x = x + 1;;
val ( !! ) : int -> int = <fun>
# !!;;
Characters 2-4:
!!;;Syntax error
# (!!);;
- : int -> int = <fun>
# !! 3;;
- : int = 4
前置演算子は -,-.もしくは
• 1文字目が!,?,~のいずれかで,
• 2文字目以降が !,$,%,*,+,-,.,/,:,<,=,>,?,@,^,|~のいずれか
からなる文字列である.一見,前置演算子は関数の名前として記号が使うためのものだけのように見 えるが,実際は構文解析で違いが現れる.前置演算子は通常の関数適用よりも結合の優先度が高く,
f !! xは,f (!! x) と解釈される.(f g xが(f g) x と解釈されることと比較せよ.)
中置演算子同士の優先度は,名前から決まる.表4.1は優先度の高いものから並べたものである.
コンストラクタなど,未出の概念,記号がでてきているがとりあえず無視しておいてよい.
表4.1: 演算子の優先順位と結合
演算子 結合
前置演算子 –
関数適用 左
コンストラクタ適用 –
前置演算子としての-,-. –
**で始まる名前 右
*,/,%,で始まる名前および mod 左
+,-で始まる名前 左
:: 右
@,^で始まる名前 右
=,<など比較演算子,その他の中置演算子 左
not –
&,&& 左
or,|| 左
, –
<-,:= 右
if –
; 右
let,match,fun,function,try –