特性系統的技能樹

更新 發佈閱讀 6 分鐘

上一篇文章提到有些介面不應被繼承,但物件導向的子類別只能繼承父類別的介面,因而產生一些不合適的介面實作。trait/typeclass則沒有這種繼承機制,這似乎使需要繼承的特性無法直接使用。然而函數式導向比起繼承,更適合使用組合,根本不需要使用繼承疊加特性。


資料類型的定義往往跟怎麼建構模型相關,透過類型我們可以知道哪些資訊與功能是關聯在一起的,而如何分類、拆解變數就是一門藝術。這門藝術有幾個流派,而物件導向與函數式編程就是其中之一。物件導向建議把資料抽象化,將其中的共同變數與相關方法蒐集起來定義成父類別。繼承就是透過增加資料結構的成員變數與方法達成擴展,讓我們可以描述更複雜的模型。而函數式編程傾向使用固定的資料結構描述模型,並利用組合構造出更複雜的系統,也就是定義一個新類型將多個資料透過成員變數組合成新的資料。我們可以透過為類型實作trait/typeclass讓物件擁有關聯方法,但與定義類別相比,這些方法不會因為組合而被繼承到外部類型,若要繼承就必須手動實作trait/typeclass並把方法委派到成員變數上。繼承成員的特性本來就不是常見的操作,因為成員自己的特性應該獨立於外部類型,而不是為了擴展而定義的。若要為擴展而定義類型與特性,應該使用泛型把可擴展的部分參數化,這種方法可以把未知的擴展部分也納入約束之中。因此可以說物件導向是向外的抽象化,它把類別之間的交集作為內部的/核心的特性抽取出來,其他特性只是它的外部延伸;而函數式則是向內的抽象化,它把特性看作是一種約束而不是某些結構的共通部分,而約束的廣適性代表了它的抽象程度,所謂的擴展只不過是其中的一個更多約束的實例。在這裡約束並不是壞事,它並不是指你不應該做什麼,這裡是指對未知的約束,約束越強,代表類型越明確,能做的事也就越多。


特性系統的擴展是藉由要求實作此特性的類型也必須實作另一個特性,特性的定義如 trait Ord: Eq { … } 指的是實作Ord的類型也必須實作Eq,而它們之間必須符合:a.eq(b)的結果必須是 a.cmp(b) == Ordering::Equal,這通常會寫在說明文件裡。這種隱含關係形成了特性的延展關係,這就像是介面的延展。在上面rust的例子中,雖然它寫成像是子類的語法,但它只是以下寫法的語法糖:trait Ord where Self: Eq { … }。其中where是用來描述這個trait的約束,其他類型參數的約束通常都會放在這裡,當然Self也不例外,而這跟介面很不一樣,因為介面沒有Self這種類型參數。在PureScript則是寫成class Eq a <= Ord a where …,其中箭頭方向是向左的(跟Haskell相反),它可以看作是一個從Ord a到Eq a的函式。它並不是要你實作這樣的函式,而是編譯器會為你產生這種函式,並在你擁有Ord a的實作時幫你使用這個函式得到Eq a。為了讓這個函式能夠正確建立,你必須在實作Ord MyType同時實作Eq MyType。這種特性的延展關係形成一棵技能樹(有向無環圖),要實作尾端的特性就必須同時實作前面的特性。反過來說,當我們有了一個特性實作,就也能存取前面所有的特性。介面也有一樣的功能,但特性的延展不限於單一的Self類型,因為Self對特性而言只是一個類型參數。


實作特性的方法與介面類似,你必須宣告你要實作哪個特性,並寫下關聯方法的實作。跟介面不一樣的是,我們可以分別對不同的類型參數實作特性,例如可以同時為一個類型MyCounter實作AddAssign<u8>和AddAssign<&[u8]>,甚至可以根據所有可能的類型參數T: IntoIterator<u8>實作AddAssign<T> 。一般而言,類別雖然不能像這樣實作無限多種介面,但這種情況只需要實作像是AddAssign<Iterable<u8>>的介面就行了。而特性系統的這種能力讓我們不用抹除類型資訊就能達到一樣的效果。它還能應用於更複雜的情況,例如可以為MyCounterWithType<T>根據所有可能的類型參數S實作AddAssign<&[S]>,其中他們必須符合約束T: AddAssign<S>。這種條件式的特性實作就跟帶有約束的泛型函式一樣自然,這在rust和Haskell等具有特性系統的語言很常看到,它讓我們可以根據狀況賦予類型不同的特性,而不需要使用動態檢查判斷合法性。在PureScript則寫成instance AddAssign t s => AddAssign (MyCounterawithType t) (List s) where …,箭頭前面的就是對類型參數 t, s 的約束/特性,它可以看作類似函式的東西,其中約束就是輸入參數,編譯器會在需要時使用這個函式建構出你需要的特性實作。ocaml的module function也是一樣的概念,它接受模組作為參數並構造出你要的模組,但你必須自己手動建構,編譯器是不會幫你的。這種動態的特性系統雖然很直覺,但在使用上有點麻煩。


有了條件式的特性實作,我們可以手動把成員的特性「繼承」到外部類型。例如struct ConnectionManager<Conn: Connectable> = { conn: Conn, … }透過組合為連線服務增加管理功能,如果要使用連線服務的一些方法,可以直接存取成員並使用它的特性。但如果不能,就必須把部分功能帶到外部類型,這時需要手動把它們接上:fn isClosed(&self) -> bool { self.conn.isClosed() }。如果要把一整個特性帶到外部,可以這樣:impl<Conn: Pingable> for ConnectionManager<Conn> { fn ping(&self) -> bool { self.conn.ping() } },如此只要連線服務有這種特性,管理器也同樣會有一樣的特性。雖然必須手動委派方法,但比起物件導向的繼承機制好多了,畢竟不是所有的方法都要帶出來,而且有例外時也比較好處理。如果還是覺得麻煩,可以考慮使用rust的巨集功能。事實上,所謂的繼承就只是讓子類實作父類的方法,它只是把子類物件轉型成父類,再呼叫父類實作的方法。現在只是把這部分明確寫下來,變成透過外部類型的變數取得成員,再呼叫成員實作的方法。必須明確寫下是因為繼承本身不是自然的,部分介面不應被繼承,也不應隨便暴露內部成員的方法。

留言
avatar-img
have bear的沙龍
4會員
28內容數
這不是教你如何從物件導向到函數式編程的入門教程。我會深入探討物件導向與函數式編程的差異,並討論為什麼你應該使用函數式編程並徹底放棄物件導向。
have bear的沙龍的其他內容
2024/02/15
這個系列的文章主要專注於物件導向到函數式編程的差異與分析,並針對概念與機制上的不同進行比較。很多人說物件導向和函數式編程沒有哪個比較好的問題,只有哪個比較適合的問題,然而我並不這麼認為,我透過這一系列的文章從各個角度討論它們之間的優缺點就是為了闡述我的觀點。物件導向錯在沒有理論基礎,但它贏在熟悉性,
2024/02/15
這個系列的文章主要專注於物件導向到函數式編程的差異與分析,並針對概念與機制上的不同進行比較。很多人說物件導向和函數式編程沒有哪個比較好的問題,只有哪個比較適合的問題,然而我並不這麼認為,我透過這一系列的文章從各個角度討論它們之間的優缺點就是為了闡述我的觀點。物件導向錯在沒有理論基礎,但它贏在熟悉性,
2024/02/08
前一篇文章中所提的函數式的三個機制明確說明了它關注的規則與能力具體是什麼。然而這套對於函數式編程的定義主要基於特定的類型系統,作為一個編程範式來說過於狹隘(物件導向的定義也是這樣)。更廣義的,我認為函數式編程主要依循三個原則,它們可以應用於任何程式語言,就算沒有靜態類型系統的支援也可以。例如在不管類
2024/02/08
前一篇文章中所提的函數式的三個機制明確說明了它關注的規則與能力具體是什麼。然而這套對於函數式編程的定義主要基於特定的類型系統,作為一個編程範式來說過於狹隘(物件導向的定義也是這樣)。更廣義的,我認為函數式編程主要依循三個原則,它們可以應用於任何程式語言,就算沒有靜態類型系統的支援也可以。例如在不管類
2024/02/04
前面談了那麽多函數式編程與物件導向的差異,但我們還沒定義函數式編程。就像物件導向,函數式編程沒有明確的定義,每個人對於什麼是函數式編程都有不同的看法。在這裡我會總結前面的討論,給出我對於函數式編程的觀點。 物件導向注重封裝與延展性,因此一般基於三個機制:繼承、多型、封裝,它們代表了物件導向所重
2024/02/04
前面談了那麽多函數式編程與物件導向的差異,但我們還沒定義函數式編程。就像物件導向,函數式編程沒有明確的定義,每個人對於什麼是函數式編程都有不同的看法。在這裡我會總結前面的討論,給出我對於函數式編程的觀點。 物件導向注重封裝與延展性,因此一般基於三個機制:繼承、多型、封裝,它們代表了物件導向所重
看更多
你可能也想看
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
《轉轉生》(Re:INCARNATION)為奈及利亞編舞家庫德斯.奧尼奎庫與 Q 舞團創作的當代舞蹈作品,結合拉各斯街頭節奏、Afrobeat/Afrobeats、以及約魯巴宇宙觀的非線性時間,建構出關於輪迴的「誕生—死亡—重生」儀式結構。本文將從約魯巴哲學概念出發,解析其去殖民的身體政治。
Thumbnail
《轉轉生》(Re:INCARNATION)為奈及利亞編舞家庫德斯.奧尼奎庫與 Q 舞團創作的當代舞蹈作品,結合拉各斯街頭節奏、Afrobeat/Afrobeats、以及約魯巴宇宙觀的非線性時間,建構出關於輪迴的「誕生—死亡—重生」儀式結構。本文將從約魯巴哲學概念出發,解析其去殖民的身體政治。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節旨在介紹JavaScript中的物件導向編程。內容包括類別(Class)的定義和使用,建構子的作用,以及公開,私有,受保護(Protected)等不同訪問修飾符的概念。此外,還涵蓋了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型、反射等物件導向的主要觀念。
Thumbnail
本章節旨在介紹JavaScript中的物件導向編程。內容包括類別(Class)的定義和使用,建構子的作用,以及公開,私有,受保護(Protected)等不同訪問修飾符的概念。此外,還涵蓋了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型、反射等物件導向的主要觀念。
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
Thumbnail
本文介紹了Python中的物件導向程式設計的重要概念,包括類別、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型和反射。每個概念都有對應的程式碼範例來說明其用法和功能。這些概念對於理解和使用Python進行物件導向程式設計至關重要。
Thumbnail
本文介紹了Python中的物件導向程式設計的重要概念,包括類別、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型和反射。每個概念都有對應的程式碼範例來說明其用法和功能。這些概念對於理解和使用Python進行物件導向程式設計至關重要。
Thumbnail
上篇我們已經把風格融入在一個網路之中,實現了訓練一次就可以轉換不同的圖片成我們訓練的風格,但是這樣還不夠,因為這樣每個風格都得訓練一個網路來轉換,太浪費了,那麼,我們有沒有辦法在同一個網路中訓練多個風格呢?
Thumbnail
上篇我們已經把風格融入在一個網路之中,實現了訓練一次就可以轉換不同的圖片成我們訓練的風格,但是這樣還不夠,因為這樣每個風格都得訓練一個網路來轉換,太浪費了,那麼,我們有沒有辦法在同一個網路中訓練多個風格呢?
Thumbnail
CSS 的繼承性是開發網頁樣式時的一個重要概念,它使得樣式設計更加靈活和高效,有助於提高程式碼的可讀性、一致性和可重用性,並加快開發速度,從而提供更好的開發體驗。
Thumbnail
CSS 的繼承性是開發網頁樣式時的一個重要概念,它使得樣式設計更加靈活和高效,有助於提高程式碼的可讀性、一致性和可重用性,並加快開發速度,從而提供更好的開發體驗。
Thumbnail
代理模式通過封裝原始對象來實現對該對象的控制和管理,同時不改變原始對象的行為或客戶端與該對象互動的方式,以此介入或增強對該對象的訪問和操作。
Thumbnail
代理模式通過封裝原始對象來實現對該對象的控制和管理,同時不改變原始對象的行為或客戶端與該對象互動的方式,以此介入或增強對該對象的訪問和操作。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News