程式語言類型論中,多型(英語: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.