λx. x K S K @はてな

このブログ内に記載された文章およびコードの著作権は,すべて Keisuke Nakano に帰属します.

#010 オプショナル引数

suiginto さんの記事に関連して, OCaml でのオプショナル引数の利用に関する二つの制限についてメモ*1

OCaml のように多相型と関数型を許した型体系でのオプショナル引数には, 意味論を健全にするためなどの理由からいくつかの制限が設けられている. まずは,一つ目の制限について.例えば,

let f x ?i:(i=1) = x + i
で定義される関数 f を考えよう((?i:(i=1)?(i=1) と書いてもよいが,混乱を避けるためラベル引数の略記法は使わないものにする.)). これに対し,f 3 の意味は,
  • i に相当するラベル引数がないので,デフォルトの値 1 が使われて 4
  • ただの部分適用なので (fun ?i:(i=1) -> 3 + i) とほぼ同じ振る舞いをする関数
の二通りの解釈方法が考えられる.現状の OCaml では後者が採用されているが,前者のような関数を定義したい場合には,
let f ?i:(i=1) x = x + i
と引数の順序を変えて記述すればよい.これにより,f 34 を返す. LISP などを使っている人にとってはオプショナル引数は最後にあるのが自然かもしれないが, 型を静的に与える OCaml では上記の理由からうまくいかないので注意する必要がある. (OLabl の頃は前者の解釈が行われていたが,意味論がおかしくなるとの理由から OCaml に合流する際に不採用となった.)

--- ここから先の部分に誤りがあるので修正予定.---

二つ目の制限は,オプショナル引数を持つ関数が多相型を返す場合に起こる矛盾を回避するためのものである.例えば,

let apply_3 g = g 3
で定義される関数 apply_3 を考えよう.この関数は,(int ->'a) -> 'a 型を持つ. これに対し,
let f ?i:(i=1) x = x
という ?i:int -> 'a -> 'a 型の関数 f を考えると, f 33 を返すものの, apply_3 f は型エラーになってしまう.これは,f の型と apply_3 の引数の型を照合する際,
  • apply_3f を呼ぶときにはオプショナル引数を省略しているから,apply_3 fint 型.
  • apply_3f を呼んでも部分適用かもしれないので,apply_3 f?i:int -> int 型.
などと二通りの解釈ができてしまう.型を見ただけでは,どちらが自然な解釈かは判断できない. 現在の OCaml では安全のためこれをエラーとしている. ただし,suginto さんの考察の通り, fapply_3 の返す型が単相型(あるいは上の解釈を曖昧にしない多相型)であれば, どちらの解釈かはっきりするために型エラーは起こらない.すなわち,
(fun g -> g 3) (fun ?i:(i=1) x -> x)
は型エラーを引き起こすが,
(fun g -> (g 3 : int)) (fun ?i:(i=1) x -> x);;
(fun g -> g 3) (fun ?i:(i=1) x -> (x:int));;
(fun g -> g 3) (fun ?i:(i=1) (x:int) -> x);;
はいずれも型エラーを起こさず,前者の振る舞いが可能となる.なお,後者の振る舞いを強制したい場合は,
(fun g -> g 3) (fun x ?i:(i=1) -> x) 
と引数の順序を変えればよい.

*1:二つ目の制限についての解説は JG 氏からの助言に基づいたものである.