参数多态是否与arity上的调度相同?

时间:2013-08-12 06:00:05

标签: types programming-languages type-theory

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

1 个答案:

答案 0 :(得分:6)

参数多态性

参数多态性背后的想法是你调度 - 参数化多态函数是对所有输入类型以相同的方式行为的函数。让我们考虑一个非常简单的例子(我将使用Haskell 1 ):

id x = x

这定义了一个名为id的函数,它接受一个参数x,然后返回它。这是 id 实体函数;它没有做任何事情。现在,id应该具有什么类型?它绝对是一个函数,因此对于某些input -> outputinput,它将具有output类型。我们可以说id的类型为Int -> Int;然后id 3将评估为3,但id True不会进行类型检查,这看起来很愚蠢。说id :: Bool -> Bool并不是更好;问题是逆转的。我们知道,对于id并不重要输入的类型是什么; id忽略该结构,只传递值。因此,对于任何类型aid的类型为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,而IntBool,而且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多态性

参数多态通常与各种形式的 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}}函数。那样做。