パラメトリック ポリモーフィズムがパラメータの型に依存せずにディスパッチしている場合、アリティ以外にディスパッチするものは他にあるでしょうか? それが同じでない場合、誰かが反例を提供できますか?
1 に答える
パラメトリック ポリモーフィズム
パラメトリック ポリモーフィズムの背後にある考え方は、ディスパッチしないということです。パラメトリック ポリモーフィック関数は、すべての入力タイプに対して同じように動作する関数です。非常に単純な例を考えてみましょう (Haskell 1を使用します):
id x = x
id
これは、 1 つの引数 を取りx
、それを返すという名前の関数を定義します。これはidエンティティ関数です。それは何もしません。さて、どんなタイプid
がいいでしょうか?これは間違いなく関数なので、一部の型と. type ;があると言えます。thenは に評価されますが、タイプチェックは行われません。これはばかげているようです。言うことは良くありません。問題は逆です。の場合、入力の型は問題ではないことがわかっています。その構造を無視し、値を渡すだけです。したがって、任意のタイプの場合、タイプがありますinput -> output
input
output
id
Int -> Int
id 3
3
id True
id :: Bool -> Bool
id
id
a
id
a -> a
、そしてこれを明示的に書くことができます:
id :: a -> a
id x = x
Haskell では、型内の小文字の識別子は普遍的に量化された変数です。上記の署名は、明示的に を書くことが一部の言語拡張でのみ有効であるid :: forall a. a -> a
ことを除いて、私が書いた場合と同じです。forall
恒等関数は、パラメトリック多相関数の最も単純な例であり、パラメトリック関数は単純にデータを渡すという考えを強調しています。彼らはデータを調べて、それを使って何かをすることはできません。
もう少し興味深い関数を考えてみましょう: リストの反転です。Haskell では何らかの型のリストa
が書かれ[a]
ているので、reverse
関数は
reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
-- `x:xs` is the list whose first element is `x` and whose second element is
-- `xs`; `++` is the list-append operator.
このreverse
関数はリストの要素をシャッフルしますが、それらを操作することはありません (したがって、それらを「ディスパッチ」することはありません)。したがって、reverse
何かのリストを取得して返す必要があることはわかっていますが、それが何であるかは気にしません。
パラメトリック ポリモーフィズムの最後の例は、map
関数です。この関数は、関数f
とリストを取り、その関数をリスト内の各要素に適用します。この説明は、関数の入力または出力の型は気にしないこと、また入力リストの型も気にしないことを示していますが、それらは適切に一致する必要があります。したがって、
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs
-- In Haskell, function application is whitespace, so `map f xs` is like
-- `map(f,xs)` in a C-like language.
渡された関数の入力 (それぞれ出力) 型と入力 (それぞれ出力) リストの要素の型が一致する必要があることに注意してください。ただし、入力と出力の型が互いに異なっていてもいなくてもかまいません。
パラメトリック ポリモーフィズムとサブタイピング
コメントで、パラメトリック関数が最上位の型の値を受け入れるかどうかを尋ねます。答えはノーです。サブタイピングは、パラメトリック ポリモーフィズムから完全に独立しています。Haskellにはサブタイプの概念がまったくありませInt
ん。Java では、ジェネリックとサブタイピングの両方がありますが、2 つの機能は意味的に無関係です (フォームの境界付きポリモーフィズムを使用しない限り、ただし、これはアドホック ポリモーフィズムのフォームに似ています。これについては後で説明します)。パラメトリック ポリモーフィズムとはまさにその言葉です。Int
Bool
Bool
<T extends Super>
タイプ。これは、最上位の型を受け入れ、包含/暗黙のアップキャストに依存する関数と同じではありません。これについて考える 1 つの方法は、パラメトリック関数が追加の引数、つまりパラメーターの型を取ることです。したがってid 3
、代わりにid Int 3
; の代わりにid True
、あなたが持っているでしょうid Bool True
。Haskell では、これを明示的に行う必要がないため、構文はありません。一方、Java では、必要な場合があるため、これを反映した構文がありますCollections.<String>emptyList()
。
アドホック ポリモーフィズム
パラメトリック ポリモーフィズムは、さまざまな形式のアドホック ポリモーフィズム(1 つの関数が異なる型で異なる方法で動作できるようにするポリモーフィズム) としばしば対照的です。ここで「dispatch」が表示されます。パラメトリック ポリモーフィズムは均一性に関するものであり、アドホック ポリモーフィズムは差異に関するものです。関数がすべての型で同じように動作することを望まない場合があります。
標準の Java に似たオブジェクト指向のサブタイプ ポリモーフィズム (正式には名目上のサブタイピングと呼ばれる) は、この例です。たとえば Java では、boolean Object.equals(Object)
メソッドはサブタイプ ポリモーフィズムを使用して最初の引数をディスパッチし、適切な結果を返します。平等をパラメトリックにしたくないことは明らかです。文字列と整数の両方が等しいかどうかを正確に比較する 1 つの関数を作成することはできません。ただし、引数の実行時の型の「型ケース」チェックを実行するために.equals
も使用されることに注意してください。instanceof
このint Object.hashCode()
メソッドは、純粋にサブタイプ ポリモーフィック メソッドの例です。
Haskell は、型クラス ポリモーフィズムと呼ばれる別のアプローチを使用して、これを処理します。これがどのように機能するかの旋風ツアーです。最初に、同等性について比較可能であるとはどういう意味かを述べます (Haskell では、任意の演算子である関数名を定義して、それらを中置で使用できることに注意してください)。
class Eq a where -- To say that a type `a` is comparable for equality, implement
-- these functions:
(==) :: a -> a -> Bool -- Equality
(/=) :: a -> a -> Bool -- Inequality
-- We can also define default implementations for those functions:
x == y = not (x /= y)
x /= y = not (x == y)
次に、型クラスをインスタンス化します。たとえば、ここでは、ブール値が等しいかどうかを比較する方法について説明します。
instance Eq Bool where
True == True = True
False == False = True
_ == _ = False
-- `_` means "don't care".
また、要素が等しいかどうかを比較する場合は、適切な制約を満たす型が必要であることを指定します。たとえばelem
、要素がリストに含まれているかどうかをチェックする関数には type がEq a => a -> [a] -> Bool
あります。a
これは、「 のインスタンスでありEq
、と のリストをelem
期待し、ブール値を返す」と読むことができます。a
a
elem :: Eq a => a -> [a] -> Bool
elem _ [] = False
elem y (x:xs) = x == y || elem y xs
-- Haskell supports an infix syntax that would have allowed us to write
-- `y `elem` xs`, with the backticks around `elem`.
ここで、elem
関数はパラメトリックにポリモーフィックではありません。なぜなら、型に関する情報があるからですa
— その要素が等しいかどうかを比較できることがわかっています。したがって、すべての入力タイプに対して同じように動作するとは限りelem
ません(また、関数など、同等かどうかを比較することさえできないタイプもあります)。そのため、ここでもタイプベースのディスパッチの形式が行われています。
1 Java のような言語に精通している場合、Java での同じ関数は (含まれているクラスを無視して)
public static <T> T id(T t) { return t; }
Haskell とは異なり、Java では、instanceof
演算子を使用したり、メソッドを呼び出したりすることで、パラメトリック性を破ることができます.toString()
が、このid
関数はそれを行いません。