編程語言類型論中,多態(英語:polymorphism)指為不同數據類型的實體提供統一的接口[1],或使用一個單一的符號來表示多個不同的類型[2]

多態的最常見主要類別有:

  • 特設多態:為個體的特定類型的任意集合定義一個共同接口。
  • 參數多態:指定一個或多個類型不靠名字而是靠可以標識任何類型的抽象符號。
  • 子類型(也叫做子類型多態或包含多態):一個名字指稱很多不同的類的實例,這些類有某個共同的超類[3]

歷史

在1967年,英國計算機科學家克里斯托弗·斯特雷奇在他的講義合集《編程語言中的基礎概念英語Fundamental Concepts in Programming Languages》中[4],首次提出了特設多態和參數多態的概念。特設多態是ALGOL 68的特徵,而參數多態是ML在1975年介入的類型系統的核心特徵[5]

在1985年,彼得·瓦格納英語Peter Wegner盧卡·卡代利英語Luca Cardelli在論文中引入了術語「包含多態」來為子類型繼承建模[2],引證1967年的Simula為子類型和繼承的最早的對應實現。

類別

特設多態

克里斯托弗·斯特雷奇選擇術語「特設多態」來指稱一個多態函數可以應用於有不同類型的實際參數上,但是以來它們所應用到的實際參數類型而有不同的表現(也叫做為函數重載運算符重載[6]。在這個上下文中術語「特設」(ad hoc)不意圖表達貶義,它只是簡單的指出這種多態不是類型系統的基本特徵。在下面的Pascal/Delphi例子中,在查看Add函數的調用的時候,它好像通用的工作在各種類型之上,但編譯器對所有意圖和用途都把它們視為完全不同的兩個函數:

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.

動態類型語言中情況可能更加複雜,因為需要調用的正確函數只能在運行時間確定。

隱式類型轉換也被定義為多態的一種形式,叫做「強迫多態」[2][7]

參數多態

參數多態允許函數或數據類型被一般性的書寫,從而它可以「統一」的處理值而不用依賴於它們的類型[8]。參數多態是使語言更加有表現力而仍維持完全的靜態類型安全的一種方式。這種函數和數據類型被分別稱為「泛化函數」和「泛化數據類型」從而形成了泛型編程的基礎。

例如,可以構造連接兩個列表的一個函數append,它不關心元素的類型:它可以附加整數的列表、實數的列表、字符串的列表等等。設定「類型變量a」來指定這個列表中元素的類型。接着append可以確定類型:

forall a. [a] × [a] -> [a]

這裡的[a]指示具有類型a的元素的列表類型。我們稱對於a的所有的值,append的類型「由a參數化」。結果的列表必須由相同類型的元素組成。對於應用append的每個位置,都要為a確定一個值。

參數多態的概念適用於數據類型函數二者。可以被求值或應用於不同類型的值之上的函數叫做「多態函數」。看起來具有泛化類型性質的數據類型(比如具有任意類型的元素的列表)被指認為「多態數據類型」,就像根據它來做特殊化的泛化類型那樣。

參數多態在函數式編程之中是普遍的,在這裡它經常被簡稱為「多態」。下面的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#DelphiJava中所稱謂的泛型:

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英語John C. Reynolds(和後來的Jean-Yves Girard英語Jean-Yves Girard)正式的將這種多態概念發展為對lambda演算的擴展(叫做多態lambda演算或系統F)。任何參數多態函數都必然在能做什麼上受到限制,工作在數據的形狀而不是它的值之上,這導致了parametricity英語parametricity的概念。

子類型

面向對象程序設計中,計算機程序執行時,相同的訊息可能會送給多個不同的類別之物件,而系統可依據物件所屬類別,引發對應類別的方法,而有不同的行為。簡單來說,所謂多型意指相同的訊息給予不同的物件會引發不同的動作。比如有動物之類別,而且由動物繼承出類別貓和類別狗,並對同一源自類別動物(父類別)之一訊息有不同的響應,如類別動物有「叫」之動作,而類別貓會「喵喵」,類別狗則會「汪汪」,則稱之為多型態。

在下面的這個例子中貓和狗都是動物的子類型。過程letsHear()接受一個動物,但在傳遞給它一個子類型的時候也能正確工作:

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語言也具有宏多態了。談及多態時,默認就是指動態多態,而靜態多態則是指基於模板的多態。

參見

參考資料

Wikiwand in your browser!

Seamless Wikipedia browsing. On steroids.

Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.

Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.