多态 (计算机科学)

在编程语言和类型论中，多态（polymorphism）指为不同数据类型的实体提供统一的接口，或使用一个单一的符号来表示多个不同的类型。

多态的最常见主要类别有：

- 特设多态：为个体的特定类型的任意集合定义一个共同接口。
- 参数多态：指定一个或多个类型不靠名字而是靠可以标识任何类型的抽象符号。
- 子类型（也叫做子类型多态或包含多态）：一个名字指称很多不同的类的实例，这些类有某个共同的超类。

## 历史

在1967年，英国计算机科学家克里斯托弗·斯特雷奇在他的讲义合集《编程语言中的基础概念》中，首次提出了特设多态和参数多态的概念。特设多态是ALGOL 68的特征，而参数多态是ML在1975年介入的类型系统的核心特征。

在1985年，彼得·瓦格纳和卢卡·卡代利在论文中引入了术语“包含多态”来为子类型和继承建模，引证1967年的Simula为子类型和继承的最早的对应实现。

## 类别

### 特设多态

克里斯托弗·斯特雷奇选择术语“特设多态”来指称一个多态函数可以应用于有不同类型的实际参数上，但是以来它们所应用到的实际参数类型而有不同的表现（也叫做为函数重载或运算符重载）。在这个上下文中术语“特设”（ad hoc）不意图表达贬义，它只是简单的指出这种多态不是类型系统的基本特征。在下面的Pascal/Delphi例子中，在查看`Add`函数的调用的时候，它好像通用的工作在各种类型之上，但编译器对所有意图和用途都把它们视为完全不同的两个函数:

```Pascal
program Adhoc;

function Add(x, y : Integer) : Integer;
begin
    Add := x + y
end;

function Add(s, t : String) : String;
begin
    Add := Concat(s, t)
end;

begin
    Writeln(Add(1, 2));                   (* 打印"3"             *)
    Writeln(Add('Hello, ', 'Mammals!'));    (* 打印"Hello, Mammals!" *)
end.
```

在动态类型语言中情况可能更加复杂，因为需要调用的正确函数只能在运行时间确定。

隐式类型转换也被定义为多态的一种形式，叫做“强迫多态”。

### 参数多态

参数多态允许函数或数据类型被一般性的书写，从而它可以“统一”的处理值而不用依赖于它们的类型。参数多态是使语言更加有表现力而仍维持完全的静态类型安全的一种方式。这种函数和数据类型被分别称为“泛化函数”和“泛化数据类型”从而形成了泛型编程的基础。

例如，可以构造连接两个列表的一个函数`append`，它不关心元素的类型：它可以附加整数的列表、实数的列表、字符串的列表等等。设定“类型变量`a`”来指定这个列表中元素的类型。接着`append`可以确定类型：

`forall a. [a] &times; [a] -> [a]`

这里的`[a]`指示具有类型`a`的元素的列表类型。我们称对于`a`的所有的值，`append`的类型“由`a`参数化”。结果的列表必须由相同类型的元素组成。对于应用`append`的每个位置，都要为`a`确定一个值。

参数多态的概念适用于数据类型和函数二者。可以被求值或应用于不同类型的值之上的函数叫做“多态函数”。看起来具有泛化类型性质的数据类型（比如具有任意类型的元素的列表）被指认为“多态数据类型”，就像根据它来做特殊化的泛化类型那样。

参数多态在函数式编程之中是普遍的，在这里它经常被简称为“多态”。下面的Haskell例子展示了参数化列表数据类型和在其上的两个参数多态函数：

```Haskell
data List a = Nil | Cons a (List a)

length :: List a -> Integer
length Nil         = 0
length (Cons x xs) = 1 + length xs

map :: (a -> b) -> List a -> List b
map f Nil         = Nil
map f (Cons x xs) = Cons (f x) (map f xs)
```

参数多态在很多面向对象语言中也能获得到。例如，C++和D的模板，和在C#、Delphi和Java中所称谓的泛型:

```CSharp
class List<T> {
    class Node<T> {
        T elem;
        Node<T> next;
    }
    Node<T> head;
    int length() { ... }
}

List<B> map(Func<A, B> f, List<A> xs) {
    ...
}
```

John C. Reynolds（和后来的Jean-Yves Girard）正式的将这种多态概念发展为对lambda演算的扩展（叫做多态lambda演算或系统F）。任何参数多态函数都必然在能做什么上受到限制，工作在数据的形状而不是它的值之上，这导致了parametricity的概念。

### 子类型

在面向对象程序设计中，计算机程序执行时，相同的讯息可能会送给多个不同的类别之物件，而系统可依据物件所属类别，引发对应类别的方法，而有不同的行为。简单来说，所谓多型意指相同的讯息给予不同的物件会引发不同的动作。比如有动物之类别，而且由动物继承出类别猫和类别狗，并对同一源自类别动物（父类别）之一讯息有不同的响应，如类别动物有“叫”之动作，而类别猫会“喵喵”，类别狗则会“汪汪”，则称之为多型态。

在下面的这个例子中猫和狗都是动物的子类型。过程`letsHear()`接受一个动物，但在传递给它一个子类型的时候也能正确工作：

```Java
abstract class Animal {
    abstract String talk();
}

class Cat extends Animal {
    String talk() {
        return "Meow!";
    }
}

class Dog extends Animal {
    String talk() {
        return "Woof!";
    }
}

static void letsHear(final Animal a) {
    println(a.talk());
}

static void main(String[] args) {
    letsHear(new Cat());
    letsHear(new Dog());
}
```

多态可分为变量多态与函数多态。变量多态是指：基类型的变量（对于C++是引用或指针）可以被赋值基类型对象，也可以被赋值派生类型的对象。函数多态是指，相同的函数调用界面（函数名与实参表），传送给一个对象变量，可以有不同的行为，这视该对象变量所指向的对象类型而定。多态也可定义为“一种将不同的特殊行为和单个泛化记号相关联的能力”，变量多态是函数多态的基础。

## 实现角度类别

依据实现时做出的选择，多态可分为：

- 动态多态（dynamic polymorphism）:生效于运行期。
- 静态多态（static polymorphism）：将不同的特殊行为和单个泛化记号相关联，由于这种关联处理于编译期而非运行期，因此被称为“静态”。可以用来实现类型安全、运行高效的同质对象集合操作。C++ STL不采用动态多态来实现就是个例子。

对于C++语言，带变量的宏和函数重载机制也允许将不同的特殊行为和单个泛化记号相关联。然而，习惯上并不将这种函数多态、宏多态展现出来的行为称为多态（或静态多态），否则就连C语言也具有宏多态了。谈及多态时，默认就是指动态多态，而静态多态则是指基于模板的多态。