0

如果参数多态性在不依赖于参数类型的情况下进行调度,那么除了 arity 之外还有什么可以调度?如果不一样,有人可以提供一个反例吗?

4

1 回答 1

6

参数多态性

参数多态背后的想法是你分派——一个参数多态函数是一个对所有输入类型都以相同方式表现的函数。让我们考虑一个非常简单的例子(我将使用 Haskell 1):

id x = x

这定义了一个名为的函数id,它接受一个参数x,并返回它。这是id实体函数;它什么也没做。现在,应该id有什么类型?它绝对是一个函数,所以它会有一些和的类型。我们可以说有类型;then将评估为,但不会进行类型检查,这似乎很愚蠢。说也好不了;问题反过来了。我们知道对于,输入的类型无关紧要;忽略该结构,只是传递值。所以对于任何类型,都有类型input -> outputinputoutputidInt -> Intid 33id Trueid :: Bool -> Boolididaida -> a,我们可以明确地写成这样:

id :: a -> a
id x = x

在 Haskell 中,类型中的小写标识符是普遍量化的变量——上面的签名和我写的一样id :: forall a. a -> a,除了写forall显式只对某些语言扩展有效。

恒等函数是参数多态函数的最简单示例,它强调了参数函数只是传递数据的想法。他们无法检查数据以对其进行任何处理。

让我们考虑一个更有趣的功能:列表反转。a在 Haskell 中,编写了某种类型的列表[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 根本没有任何子类型化的概念: anInt是 an Int,而 aBool是 a Bool,两者永远不会相遇。在 Java 中,你有泛型和子类型,但是这两个特性在语义上是不相关的(除非你使用form 的有界多态<T extends Super>,但这更像是ad-hoc polymorphism的一种形式,我将在下面讨论)。参数多态性确实是它所说的:接受任何类型。这与接受顶级类型并依赖包含/隐式向上转换的函数不同。考虑它的一种方法是参数函数需要一个额外的参数:参数的类型。因此id 3,您将拥有id Int 3; 而不是id True,你会有id Bool True。在 Haskell 中,您永远不需要显式地执行此操作,因此它没有语法。另一方面,在 Java 中,有时您需要这样做,因此有反映这一点的语法,如Collections.<String>emptyList().


即席多态性

参数多态性通常与各种形式的即席多态性形成对比:多态性允许一个函数在不同类型下以不同方式表现。这就是“调度”出现的地方;其中参数多态性是关于一致性的,而临时多态性是关于差异性的。有时您希望函数在每种类型下都以相同的方式运行!

标准的类似 Java 的面向对象的子类型多态,正式称为名义子类型,就是一个例子;例如,在 Java 中,该boolean Object.equals(Object)方法使用子类型多态性来调度其第一个参数并返回适当的结果。很明显,您不希望平等是参数化的;你不能编写一个函数来准确地比较字符串和整数是否相等!但是请注意,这.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检查元素是否出现在列表中的函数类型为Eq a => a -> [a] -> Bool; 我们可以将其理解为“对于任何a 一个 的实例Eqelem期望一个a和一个as 的列表,并返回一个布尔值”:

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函数不会这样做。

于 2013-08-12T08:24:21.920 回答