多型的差異

閱讀時間約 8 分鐘

所謂的多型是讓一個函式或是資料結構能擁有多個不同的類型,其中上一篇文章所談的就是參數多型(parametric polymorphism),這篇文章將繼續討論特設多型(ad hoc polymorphism)。特設多型跟泛型的差別在於:泛型函式對於所有的類型只能有一種實作,而特設多型會根據類型有不同的實作。物件導向就是藉由改寫方法的機制實現特設多型,另外一種方法是藉由實作介面。特設多型的所有可能的實作不需要放在同一個地方,你可以在其他檔案/模組/專案寫下它對特定類型的實作,而在使用時(大部分的)編譯器會為你找出最合適的實作。這個特性使得它能用來表達抽象的概念,而非特定的函式實作,這樣才能讓程式碼具有可延展性。


多型能讓一個名詞囊括一類特性,使它們凝聚成一種抽象概念,缺乏多型的能力會讓程式碼與概念的重用性降低。當我們要為不同的類型實作某種共通的抽象操作時,我們必須使用多型。例如fn swap<A,B>(Tuple<A,B>)->Tuple<B,A>就是將所有交換Tuple元素的操作抽象化的多型函式。但並不是所有的概念都能用同一種實作描述,只有泛型是不夠用的。例如fn toString<A>(A) -> String是將變數轉換成文字以供閱讀的函式,對於每個類型都有不同的實作,這時就需要使用特設多型為每個類型實作不同的操作。特設多型讓我們能夠在不知道實際類型的情況下做出不同的行為。例如,我們沒辦法直接實作出fn sort<T>(Array<T>) -> Array<T>,因為我們不知道如何比較未知的類型T,我們至少需要知道如何比較T,因此需要給類型參數一個約束T: Ord,這代表存在函式fn compare(T, T) -> Ordering的實作,因此排序函式可以利用這個函式完成操作。如果沒有這個約束,所有類型的列表丟進去都會受到一樣的交換,如果sort({1,2,3}) == {2,3,1},那麽sort({'a','s','d'})就會得到{'s','d','a'}。物件導向的程式語言則可以透過檢查實體類型做出不同的行為,但這只能針對已知的類別進行差別處理,這並不能用來實現sort這種需要你不知道的類型的某種特性的函式。


「特設多型」的定義是指函式根據類型有不同的行為,然而這並不是最重要的功能。特設多型函式還允許讓不同類型的實作放在不同地方,你甚至可以在其他檔案/模組/專案寫下它對特定類型的實作,這使得這個函式可以被延展。因此使用(未實例化的)特設多型函式時是不可能知道它實際會怎麼運作,這強迫我們必須以抽象的方式思考。這種延展性與物件導向的介面實作機制類似,它能讓一個抽象概念(參數化類型)在不同地方被明確描述(被實作)。就如上面所提到的,像是sort這種需要非特定類型的特性的情境,大部分時候這只能依靠特設多型的可延展性實現,而這才是特設多型的最大用處。


物件導向可以藉由介面實現特設多型,它將這種函式放在一個類型名下,並給予此名稱一個概念,而定義類別時可以透過實作此介面附加針對此類型的實現。在TypeScript寫成 interface Display { toString(): String; },其中物件本身是隱含在toString的一個泛型變數。然而對於物件導向來說,這個方法是屬於物件的而非附加給Display的,所謂的「介面」只是為了介接給其他人使用的一個「特殊類別」,它並不是獨立於類別的存在。這種觀點使得介面只能在定義類別時實作,若是你新定義了一個介面想讓已有的類別實作,就必須回頭去改寫類別的實作,這樣會讓類別的定義包含各種抽象方法的實作,使得概念與關注點沒辦法集中在個別的模組內。物件導向的這種設計迫使我們必須把關注點與物件綁在一起,在這種情況下通常會將這些想要抽離的實作再分出一個類別,並以組合的方式合併。相對地,rust的trait用法類似於介面,它底下的方法被稱為關聯方法而非成員方法,因為它並不屬於此類型。為特定類型實作trait的程式碼不是寫在定義類型時,在一些情況下也可以在跟類型不同的模組實作trait,甚至可以為符合某種條件的類型實作trait。因此trait應被視為獨立於類型的存在,它是用來描述這個類型帶有什麼樣的特性,而所謂的特性並不是資料,因此不應跟用來描述資料的類型綁在一起。rust也允許把方法綁定到類型名稱上,它只代表這些方法與此類型有很緊密的關係,但它不具有繼承的特性,所有繼承能實現的功能都應交由trait完成,因為這是trait的職責。


雖然rust的trait system用起來很像介面,但它其實來自於Haskell的typeclass,因此有些特性用介面的角度來看很神奇。rust的trait寫成類似於介面的語法,例如 where A: Add<B>代表A擁有加上B的特性,它更適合寫成addImpl: Add<A, B>,也就是addImpl是描述”A加上B”這個特性的物件,但這個物件是由編譯器在編譯時期幫你建構的。在Haskell則是寫成Add a b => …,這個寫法就好像Add a b是某種參數,但它也是由編譯器幫你填上的,因此並不需要煩惱這點。trait的實作代表的是一種描述某種類型的特性的函式集,而這套函式集會綁定到當前的情境,因此不需明確地說明你是要使用哪種trait的函式。而且一般來說是不會遇到同時使用同一個trait但多種實作的情況,因此Haskell和rust都沒有給trait/typeclass參數一個名稱(PureScript可以,但沒有用途),也不能為同樣的類型實作兩個版本,Haskell甚至要求typeclass必須能唯一地決定。rust跟Haskell的這些機制都是發生在編譯時期,因此實際上並沒有addImpl這個物件,前面提的這些看法只是我的想像,事實上可能不是這麼運作的,但ocaml真的把trait的實作當作是函式集的物件。ocaml把類型與相關函式包成一個模組,就像一個struct一樣,而這個模組也有類型。它的類型包含了定義的類型名稱、定義的變數類型和函式類型,其中它們的類型可以跟它自己定義的類型有關,具有相同類型結構的模組都被當作是同樣類型的。若要讓某個類型具有可以比較的特性,只要定義有相應結構的模組就行了,到時如果某個函式要用到這個特性,就把這個模組傳給他。例如你可以為類型mytype定義一個模組module mytypeComparable: sig type t = mytype; val compare: t -> t -> int; end,若要為mytype list做排序時,只要把他丟給sort: (module M: Comparable with type t = 't) -> 't list -> 't list就行了。


這種只看結構的類型稱做鴨子型別(duck type),我們不需要明說它實作了哪個特性,使用上會直接假設它符合我們的要求,這種規則跟直接傳入函式幾乎是一樣的:sortBy: ('t -> 't -> int) -> 't list -> 't list。使用鴨子型別會產生隱藏的依賴關係,當你為了排序而實作一個模組時,沒人知道知道這個模組描述什麼概念,直到把它傳入sort函式。TypeScript的介面也是鴨子型別,定義的名稱實際上只是別名,但language server還是會記得你使用的介面是什麼名稱,以方便在編輯時提供資訊。為資料類型或是特性取唯一的名字不只是為了方便,最主要是為了讓他們擁有明確的意義,而非只是看起來像什麼。同樣是類型為t -> t -> int的函式,如果沒有說明,很難知道他是做什麼的。而把它包裝成名為Comparable的特性,很快就能知道他是用來比較順序的,如果還不知道可以去看Comparable的說明文件。包裝成特性還能讓它應該符合哪些約束變得更明瞭,而這些約束是組成特性很重要的部分,或者說只有當這些約束存在才能形成特性本身。


4會員
28內容數
這不是教你如何從物件導向到函數式編程的入門教程。我會深入探討物件導向與函數式編程的差異,並討論為什麼你應該使用函數式編程並徹底放棄物件導向。
留言0
查看全部
發表第一個留言支持創作者!
have bear的沙龍 的其他內容
前幾篇文章討論了類型系統的合理性,而這會影響我們對於變數與函式是什麼的理解。其中泛型是當中很重要的一個元素,很多討論都是基於泛型的使用。泛型會大大地增加類型系統的複雜度,因此有些語言選擇不提供泛型(go),但缺少泛型又會使簡單的容器都無法用類型精確描述。泛型的強大必須結合有紀律的類型系統才能顯現,但
函式跟資料結構一樣都有類型,它不只是特定於函式的概念,而是跟int, tuple<bool,float>等類型同等的概念。在c++函式的類型可以寫做如std::function<int(float,float)>,它可以放在tuple, array等容器裡,當然也可以作為函式的參數或是傳回值,如st
前幾篇文章在討論類型時,只討論了乘法與加法類型,這只是最基礎的類型構造方式,另外還有函式類型和泛型等概念還沒討論。在討論函式的類型之前,必須先討論函式的正確用法。對於程序式編程來說,函式是一段可重複使用的執行代碼,輸入的參數是用來控制執行行為的,因此比起函式(function)更應該稱它為程序(pr
物件導向程式語言的類型系統總是不合理的,這些程式語言承襲至舊的語言不好的特性,而人們並沒有意識到它的問題,或者比起健全(soundness)它們更注重熟悉(familiarity)。多數的物件導向程式語言都源自c/c++,而c++有太多糟糕的設計,然而同一時期出來的Haskell卻很少有類似的問題。
寫上一篇文章時我意識到,類型,類別,型別這幾個詞在物件導向當道的現代變得有些模糊,常常會不小心當成是物件導向的類,但我指的其實是資料類型。在英文中,我常常這樣區分它們:物件導向的類是class,代表的是抽象的物件模型,而類型是type/data type,代表的是實際的資料結構。正如上一篇文章所說,
承接上一篇文章,現代的物件導向已經走偏了,他就像null pointer,很容易出現不好的設計。自從我深入學習函數式編程後,漸漸發現物件導向的不合理的設計,而學習rust之後更讓我開始討厭物件導向,rust幾乎把所有我認為不好的地方都修正了。這個系列的文章我將會一一比較物件導向與rust的差異。這篇
前幾篇文章討論了類型系統的合理性,而這會影響我們對於變數與函式是什麼的理解。其中泛型是當中很重要的一個元素,很多討論都是基於泛型的使用。泛型會大大地增加類型系統的複雜度,因此有些語言選擇不提供泛型(go),但缺少泛型又會使簡單的容器都無法用類型精確描述。泛型的強大必須結合有紀律的類型系統才能顯現,但
函式跟資料結構一樣都有類型,它不只是特定於函式的概念,而是跟int, tuple<bool,float>等類型同等的概念。在c++函式的類型可以寫做如std::function<int(float,float)>,它可以放在tuple, array等容器裡,當然也可以作為函式的參數或是傳回值,如st
前幾篇文章在討論類型時,只討論了乘法與加法類型,這只是最基礎的類型構造方式,另外還有函式類型和泛型等概念還沒討論。在討論函式的類型之前,必須先討論函式的正確用法。對於程序式編程來說,函式是一段可重複使用的執行代碼,輸入的參數是用來控制執行行為的,因此比起函式(function)更應該稱它為程序(pr
物件導向程式語言的類型系統總是不合理的,這些程式語言承襲至舊的語言不好的特性,而人們並沒有意識到它的問題,或者比起健全(soundness)它們更注重熟悉(familiarity)。多數的物件導向程式語言都源自c/c++,而c++有太多糟糕的設計,然而同一時期出來的Haskell卻很少有類似的問題。
寫上一篇文章時我意識到,類型,類別,型別這幾個詞在物件導向當道的現代變得有些模糊,常常會不小心當成是物件導向的類,但我指的其實是資料類型。在英文中,我常常這樣區分它們:物件導向的類是class,代表的是抽象的物件模型,而類型是type/data type,代表的是實際的資料結構。正如上一篇文章所說,
承接上一篇文章,現代的物件導向已經走偏了,他就像null pointer,很容易出現不好的設計。自從我深入學習函數式編程後,漸漸發現物件導向的不合理的設計,而學習rust之後更讓我開始討厭物件導向,rust幾乎把所有我認為不好的地方都修正了。這個系列的文章我將會一一比較物件導向與rust的差異。這篇
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
在應用行為分析的獎勵品,務必以「實體」漸進方式到「無形」,也就是口頭式、手勢等。 而多樣化的型式,務必注意。 用「驚喜的方式」進行軟化自閉症者的固著性,並提升多樣化的獎勵方式 在愛培自閉症基金的訓練,有用到隨機的獎勵品。 有時是驚喜箱,有時是隨機的泡泡。 簡單說,這類的獎勵品,需要培養「彈
Thumbnail
最近在調整跟優化效能時,又來複習了 Multi-threading 和 Multiprocessing 的概念,在面對大數據時,能夠有效利用多核心處理器的能力,對於提升程式執行效能至關重要。 Python 提供多線程(Multi-threading) 和多進程 (Multiprocessing)
Thumbnail
地理科的海岸地形、地球科學的海水運動,皆會遇到需繪製「波浪地形圖」的時候,在簡報中該怎麼繪製呢?「曲線圖案」描邊繪製為最速解。在簡報中繪製圖形時,首要的關鍵思維是:先判斷要繪製的圖形,轉折處是圓滑還尖角,圓滑就選曲線繪製,尖角就選手繪多邊形繪製。這個關鍵起手式,能讓你不再圖形按鈕瞎按瞎摸,時間虛耗中
Thumbnail
認識不少設計師朋友抱怨說,工程師的薪水怎麼這麼高,這輩子都到不了。一位創業的大大也說過 月薪四萬的人很認真可以賺到五萬,月薪十萬的人很認真可以賺到十二萬,但月薪四萬在怎麼認真也不會變成十二萬,而他們的差異不是認真,而是在的行業。 很多人喜歡說薪水不一樣是因為要得學歷、念的學校、進入門檻的差異,但其實
Thumbnail
來到台北市北投區旅遊妳會如何玩法呢??北投區當地人有個玩法;大家來參考吧!!!那就是來到公園走走,復興公園中的溫泉泡腳池,是以溫泉聞名的北投區三大溫泉泡腳池之一。除了復興公園外,另外兩處分別是泉源公園與硫磺谷。而復興公園的是三處中,交通最為方便的一個。 復興公園相關資訊:: 地址: 台北市北投區珠海
Thumbnail
在這快速變化的時代裡,一定要能跟上變化做出適應與調整,如果故我的一昧依靠過去經歷推進,恐怕很快就會被這詭譎的漩渦沖入歷史之中了。
Thumbnail
人類一出生就開始陸續背負了許多無形的債務? 許多人大部分的欠債並非來自前世或累世,而是來自於今世? 一般人都以為欠債與冤親債主之間的債務大部分都來自於前世或累世, 其實並不竟然,除非在前世或累世中是罪大惡極,冤債、債務眾多外 , 否則大部分的欠債並非來自前世或累世,而是來自於今世。
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
在應用行為分析的獎勵品,務必以「實體」漸進方式到「無形」,也就是口頭式、手勢等。 而多樣化的型式,務必注意。 用「驚喜的方式」進行軟化自閉症者的固著性,並提升多樣化的獎勵方式 在愛培自閉症基金的訓練,有用到隨機的獎勵品。 有時是驚喜箱,有時是隨機的泡泡。 簡單說,這類的獎勵品,需要培養「彈
Thumbnail
最近在調整跟優化效能時,又來複習了 Multi-threading 和 Multiprocessing 的概念,在面對大數據時,能夠有效利用多核心處理器的能力,對於提升程式執行效能至關重要。 Python 提供多線程(Multi-threading) 和多進程 (Multiprocessing)
Thumbnail
地理科的海岸地形、地球科學的海水運動,皆會遇到需繪製「波浪地形圖」的時候,在簡報中該怎麼繪製呢?「曲線圖案」描邊繪製為最速解。在簡報中繪製圖形時,首要的關鍵思維是:先判斷要繪製的圖形,轉折處是圓滑還尖角,圓滑就選曲線繪製,尖角就選手繪多邊形繪製。這個關鍵起手式,能讓你不再圖形按鈕瞎按瞎摸,時間虛耗中
Thumbnail
認識不少設計師朋友抱怨說,工程師的薪水怎麼這麼高,這輩子都到不了。一位創業的大大也說過 月薪四萬的人很認真可以賺到五萬,月薪十萬的人很認真可以賺到十二萬,但月薪四萬在怎麼認真也不會變成十二萬,而他們的差異不是認真,而是在的行業。 很多人喜歡說薪水不一樣是因為要得學歷、念的學校、進入門檻的差異,但其實
Thumbnail
來到台北市北投區旅遊妳會如何玩法呢??北投區當地人有個玩法;大家來參考吧!!!那就是來到公園走走,復興公園中的溫泉泡腳池,是以溫泉聞名的北投區三大溫泉泡腳池之一。除了復興公園外,另外兩處分別是泉源公園與硫磺谷。而復興公園的是三處中,交通最為方便的一個。 復興公園相關資訊:: 地址: 台北市北投區珠海
Thumbnail
在這快速變化的時代裡,一定要能跟上變化做出適應與調整,如果故我的一昧依靠過去經歷推進,恐怕很快就會被這詭譎的漩渦沖入歷史之中了。
Thumbnail
人類一出生就開始陸續背負了許多無形的債務? 許多人大部分的欠債並非來自前世或累世,而是來自於今世? 一般人都以為欠債與冤親債主之間的債務大部分都來自於前世或累世, 其實並不竟然,除非在前世或累世中是罪大惡極,冤債、債務眾多外 , 否則大部分的欠債並非來自前世或累世,而是來自於今世。