let多相と値多相
先日、関数合成を使うと、無駄なletやfunを減らせていいよね、という話をしました。
let (>>) f g x = g (f x) |
で、関数合成を多用していると、2引数関数に対しても合成をしたくなる局面が出てきます。
たとえば、compareを元にしてequalを作ろうと思った場合、素直なコードは以下になります。
let equal x y = compare x y = |
これは、「xとyをcompareして、その値が0と等しいかを検査する」という関数になります。
これをcompareと(=) 0の関数合成で書こうとするとさっきの関数合成関数ではできません。(後述するカリー化とアンカリー化を使えば出来ますが)
というわけで、こういう場合は2引数関数合成が必要になってきます。
let (>>>) f g x y = g (f x y) let (>>>) f g x y = f (g x y) |
それじゃ3引数関数、4引数関数も別に作るのか、と言われると実用上問題がありそうですが、それは置いておいて。
この合成関数を使えば、先程のは以下のように書くことができます。
let eq = (=) let equal = compare >>> eq |
(=) 0のところがあまり綺麗じゃなかったので、予め(=)をeqで別名定義しています。
とりあえず、これでequalを定義することが出来ました。
……なんですが、このequalの定義を実際に使ってみると問題が発生します。
# let equal = compare >>> eq ;; val equal : '_a -> '_a -> bool = # equal 1 1;; - : bool = true # equal . .;; Error: This expression has type float but an expression was expected of type int |
多相定義してるはずなのに、最初にintで比較した後にfloatで比較しようとすると、「intじゃないよ!」と怒られてしまいます。
しかもよく見ると、equalの型が「’a」ではなく「’_a」になってしまいます。
この「’_a」というのは一度だけ任意の型で使用できる型変数で、この制限を「値多相」と呼ぶそうです。(参考)
普段使う「’a」の方がletで値を定義したときに導入されるのに対し、「’_a」はletで関数適用など値にするまでに計算を要する場合に導入されるそう。
なんでこんな制限があるのか、というのは、副作用のある式に関係するらしいですが詳しく調べていません。
今回のequalでは、右辺が関数適用になっているため値多相になってしまっています。
これを回避するためには、素直に2引数を明示するか、1つめの引数だけ明示して次のようにします。
let equal x = compare x >> eq |
この場合は、右辺は関数型の値になるため値多相を回避できます。
ま、片方引数を指定する位なら、素直に書いたほうがいいでしょうが。
ちなみに、タプルで値を取る1引数関数とカリー化された2引数関数を変換するcurry、uncurryなんてのを作っても、同様の理由で適用時に値多相になり、あまり使い勝手が良くないものになります。
局所的に使う分には使えなくもないですが、以下のように関数を定義しちゃうと一般的には使えないものになってしまいます。
# let curry f x y = f (x,y);; val curry : ('a * 'b -> 'c) -> 'a -> 'b -> 'c = # let uncurry f (x, y) = f x y;; val uncurry : ('a -> 'b -> 'c) -> 'a * 'b -> 'c = # let tuplecompare = uncurry compare;; val tuplecompare : '_a * '_a -> int = # tuplecompare (,);; - : int = # tuplecompare (.,.);; Error: This expression has type float * float but an expression was expected of type int * int |
あと、カリー化とアンカリー化を使うと、equalが通常の関数合成を使って書けます……余計読みづらいですね。
そもそも、どちらにしろ値多相なので使い物になりません。
let equal = uncurry compare >> eq |> curry |
さて、多相ということでついでに、実はバリアントも多相に出来るよ、という話。
割と入門マニュアルの最初の方にある話なのに、多相について調べてて初めて知りました。
なんか解説記事的なのを書こうかと思いましたが、osiireさんのサイトがものすごくまとまっているので、そちらにリンクを貼っておくだけに留めます。
こういう場合はトラックバックでいいんだろうか……