注意点
この文章は、自分が理解している範囲内でまとめたものなので、
正確性に欠ける、または、間違っている点を含んでいる可能性がありますのでご注意ください。
Polymorphic function
Haskell には polymorphic function と呼ばれるものがあり、例えば、 length 関数の型は、
length :: [a] -> Int
となっていて、任意の型 a について、実際の型を適用して具体化することができる。
-- GHCi, version 7.8.3: http://www.haskell.org/ghc/ :? for help
ghci> length ([2,3,4] :: [Int])
3
ghci> length ("string" :: [Char])
6
という具合。
forall
ここで、この polymorphic function をそのまま、関数の引数として渡したいとする。
単純に考えると型は以下のようになるが、
foo :: ([a] -> Int) -> Int
foo f = f [1,2,3] + f "string"
これはエラーになってしまう。
-- => Couldn't match type ‘a’ with ‘Char’
上記の定義では、 foo に渡す関数 f は polymorphic function ではなく「 a は任意の型のうち何かの型を適用して具体化したもの」、
つまり [Int] -> Int, [Char] -> Int などのどれか一つ、と解釈されてしまう。
そのため f [1, 2, 3]
と f "string"
のように異なる型を適用することはできない。
では、どうすればよいのか。
そもそも、 polymorphic function の型:
length :: [a] -> Int
は、本来は、
length :: forall a. [a] -> Int
という具合に forall a.
で全体を修飾したものを意味する。
同様に foo 関数の型は、
foo :: forall a . ([a] -> Int) -> Int
となる。
この forall a.
は「任意の型 a に対して」という意味になるのだけれど、
上記の場合では forall a.
は foo 関数の型全体を修飾しているので、 foo 関数の定義内部では、型変数 a は何かひとつの型に定まったもの、として扱われてしまう。
このため、 polymorphic function のまま渡すことができない。
そこで、 polymorphic function のままの型で渡せるように、この forall a.
が修飾している部分を、以下のように明示的に限定してやる必要がある。
{-# LANGUAGE RankNTypes #-}
foo' :: (forall a. [a] -> Int) -> Int
foo' f = f [1,2,3] + f "string"
ghci> foo' length
9
と、確かに polymorphic function が渡せるようになった。上記のような型を定義するには RankNTypes
という GHC 拡張を使う必要がある点に注意。
Higher rank type
length 関数や foo 関数のように forall が型全体 ( 最外側 ) を修飾しているような型を rank-1 type と言い、
foo' 関数のように rank-1 type を引数に持つ関数の型を rank-2 type と言う。
一般的に rank-n type は rank-(n-1) type の型を引数に持つ、とのこと。
もう一度、 rank-1 type と rank-2 type の意味の違いを整理しておくと、
rank-1 type
foo :: forall a. (a -> Int) -> (Char, Bool)
とした時の、 foo の定義内での型変数 a は、任意の型のうち、何らかのひとつの型を表す。
rank-2 type
foo' :: (forall a. a -> Int) -> (Char, Bool)
とした時の、 foo' の定義内での型変数 a は、また具体化してないので、任意の型のうち、全ての型を (同時に) 表す。