如果参数多态在不依赖于参数类型的情况下进行调度,那么除了arity之外还有什么可以调度?如果不一样,有人可以提供一个反例吗?
答案 0 :(得分:6)
参数多态性背后的想法是你不调度 - 参数化多态函数是对所有输入类型以相同的方式行为的函数。让我们考虑一个非常简单的例子(我将使用Haskell 1 ):
id x = x
这定义了一个名为id
的函数,它接受一个参数x
,然后返回它。这是 id 实体函数;它没有做任何事情。现在,id
应该具有什么类型?它绝对是一个函数,因此对于某些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
相同,除了写{{1}显式仅对某些语言扩展有效。
标识函数是参数化多态函数的最简单示例,它强调了参数函数只是传递数据的想法。他们无法检查数据,无法用它做任何事情。
让我们考虑一个稍微有趣的功能:列表反转。在Haskell中,某些类型forall
的列表被写为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
请注意,传入函数的输入(分别为输出)类型和输入(分别为输出)列表的元素类型必须匹配;但是,输入和输出类型可以彼此不同或不相同,我们不在乎。
您在评论中询问参数函数是否只接受顶部类型的值。答案是否定的:子类型完全独立于参数多态。 Haskell根本没有任何子类型的概念: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.
是Int
,而Int
是Bool
,而且twain不会相遇。在Java中,你有泛型和子类型,但这两个特性在语义上是无关的(除非你使用Bool
形式的有界多态,但这更像是的一种形式ad-hoc多态性,我将在下面讨论)。参数多态实际上就是它所说的:接受任何类型的函数。这与接受顶级类型并依赖包含/隐式向上转换的函数不同。考虑它的一种方法是参数函数采用另一个参数:参数的类型。因此,<T extends Super>
代替id 3
,而不是id Int 3
;而不是id True
,您将拥有id Bool True
。在Haskell中,您永远不需要明确地执行此操作,因此没有语法。另一方面,在Java中,您有时需要,因此有反映这一点的语法,如Collections.<String>emptyList()
。
参数多态通常与各种形式的 ad-hoc多态形成对比:多态性允许一个函数在不同类型中以不同方式运行。这就是&#34;发送&#34;出现;参数多态性是关于均匀性的,ad-hoc多态性是关于差异的。有时你不希望某个功能在每种类型中以相同的方式运作!
标准的类Java面向对象的子类型多态,正式称为名义子类型,就是这样的一个例子;例如,在Java中,boolean Object.equals(Object)
方法使用子类型多态来调度其第一个参数并返回适当的结果。很明显,你不希望平等是参数化的;你不能编写一个函数来准确地比较字符串和整数是否相等!但请注意,.equals
也使用instanceof
来执行&#34; typecase&#34;检查参数的运行时类型; 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
函数具有类型Eq a => a -> [a] -> Bool
;我们可以将其视为&#34;对于任何a
,Eq
的实例,elem
需要a
和列表a
s,并返回一个布尔值&#34;:
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
将不以相同的方式运行(并且有些类型我们甚至可以比较相等,例如函数),所以&& #39;也是一种基于类型的调度形式。
1 如果您更熟悉Java等语言,Java中的相同功能将是(忽略包含类)
public static <T> T id(T t) { return t; }
请注意,与Haskell不同,Java允许您使用instanceof
运算符或调用始终可用的.toString()
等方法来违反参数,但我们的id
函数不会使用{{1}}函数。那样做。