特性系統的技能樹

更新於 發佈於 閱讀時間約 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
4會員
28內容數
這不是教你如何從物件導向到函數式編程的入門教程。我會深入探討物件導向與函數式編程的差異,並討論為什麼你應該使用函數式編程並徹底放棄物件導向。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
have bear的沙龍 的其他內容
類似於trait/typeclass的特性系統能提供程式「延展性」,它能讓函式針對不同的類型做出不同的行為。這種機制與物件導向的繼承非常像,然而特性系統的彈性比較大一點,而且概念上也有一些差別。為了探討討論這些差異,我們必須深入了解繼承機制到底是什麼。 繼承並不是建立子類關係的唯一方法。所謂的
所謂的多型是讓一個函式或是資料結構能擁有多個不同的類型,其中上一篇文章所談的就是參數多型(parametric polymorphism),這篇文章將繼續討論特設多型(ad hoc polymorphism)。特設多型跟泛型的差別在於:泛型函式對於所有的類型只能有一種實作,而特設多型會根據類型有不同
前幾篇文章討論了類型系統的合理性,而這會影響我們對於變數與函式是什麼的理解。其中泛型是當中很重要的一個元素,很多討論都是基於泛型的使用。泛型會大大地增加類型系統的複雜度,因此有些語言選擇不提供泛型(go),但缺少泛型又會使簡單的容器都無法用類型精確描述。泛型的強大必須結合有紀律的類型系統才能顯現,但
函式跟資料結構一樣都有類型,它不只是特定於函式的概念,而是跟int, tuple<bool,float>等類型同等的概念。在c++函式的類型可以寫做如std::function<int(float,float)>,它可以放在tuple, array等容器裡,當然也可以作為函式的參數或是傳回值,如st
前幾篇文章在討論類型時,只討論了乘法與加法類型,這只是最基礎的類型構造方式,另外還有函式類型和泛型等概念還沒討論。在討論函式的類型之前,必須先討論函式的正確用法。對於程序式編程來說,函式是一段可重複使用的執行代碼,輸入的參數是用來控制執行行為的,因此比起函式(function)更應該稱它為程序(pr
物件導向程式語言的類型系統總是不合理的,這些程式語言承襲至舊的語言不好的特性,而人們並沒有意識到它的問題,或者比起健全(soundness)它們更注重熟悉(familiarity)。多數的物件導向程式語言都源自c/c++,而c++有太多糟糕的設計,然而同一時期出來的Haskell卻很少有類似的問題。
類似於trait/typeclass的特性系統能提供程式「延展性」,它能讓函式針對不同的類型做出不同的行為。這種機制與物件導向的繼承非常像,然而特性系統的彈性比較大一點,而且概念上也有一些差別。為了探討討論這些差異,我們必須深入了解繼承機制到底是什麼。 繼承並不是建立子類關係的唯一方法。所謂的
所謂的多型是讓一個函式或是資料結構能擁有多個不同的類型,其中上一篇文章所談的就是參數多型(parametric polymorphism),這篇文章將繼續討論特設多型(ad hoc polymorphism)。特設多型跟泛型的差別在於:泛型函式對於所有的類型只能有一種實作,而特設多型會根據類型有不同
前幾篇文章討論了類型系統的合理性,而這會影響我們對於變數與函式是什麼的理解。其中泛型是當中很重要的一個元素,很多討論都是基於泛型的使用。泛型會大大地增加類型系統的複雜度,因此有些語言選擇不提供泛型(go),但缺少泛型又會使簡單的容器都無法用類型精確描述。泛型的強大必須結合有紀律的類型系統才能顯現,但
函式跟資料結構一樣都有類型,它不只是特定於函式的概念,而是跟int, tuple<bool,float>等類型同等的概念。在c++函式的類型可以寫做如std::function<int(float,float)>,它可以放在tuple, array等容器裡,當然也可以作為函式的參數或是傳回值,如st
前幾篇文章在討論類型時,只討論了乘法與加法類型,這只是最基礎的類型構造方式,另外還有函式類型和泛型等概念還沒討論。在討論函式的類型之前,必須先討論函式的正確用法。對於程序式編程來說,函式是一段可重複使用的執行代碼,輸入的參數是用來控制執行行為的,因此比起函式(function)更應該稱它為程序(pr
物件導向程式語言的類型系統總是不合理的,這些程式語言承襲至舊的語言不好的特性,而人們並沒有意識到它的問題,或者比起健全(soundness)它們更注重熟悉(familiarity)。多數的物件導向程式語言都源自c/c++,而c++有太多糟糕的設計,然而同一時期出來的Haskell卻很少有類似的問題。
你可能也想看
Google News 追蹤
Thumbnail
在創作的路上真的很多人問我說 到底要怎麼做出符合自己期待 但又可以表現得很有美感的作品?🥹 這個問題真的應該是每個創作者都一直在學習的課題吧!
提問的內容越是清晰,強者、聰明人越能在短時間內做判斷、給出精準的建議,他們會對你產生「好印象」,認定你是「積極」的人,有機會、好人脈會不自覺地想引薦給你
Thumbnail
在重新撰寫《重構命運》中涉及雙生火焰和不同靈魂伴侶關係的篇章之前,我和本源/高我聊了聊這些年我對此的一些心得和困惑,集結成一篇QA集供有興趣的讀者參考,近期可能也會隨興發其它議題統整的QA集。
Thumbnail
羽毛螢石被譽為智慧之石,主要成分為氟化鈣與矽,常呈現紫、綠、藍、黃等多種顏色。它不僅以其美麗的透明度和光澤受到喜愛,還能增強思維清晰度,適合學生和創意工作者佩戴。此篇文章探討羽毛螢石的特點、適用人群及其對於靈性提升的幫助,以及不同顏色螢石的獨特功效,讓你瞭解如何善用此寶石以提高學習和工作效率。
Thumbnail
和匱乏比起來,我們較難因應豐盛。凡事順遂的時候要嚴守紀律,在心理上拋去身外之物十分困難,但這正是一個人最需要紀律的時候。 建立一個系統,其中不管誰倒下去,都不會拖累其他人,持續不斷的失敗,有助於保存整個系統。 為了穩定而設法取得穩定,在經濟和外交上是一種冤大頭遊戲。(銀行大到不能倒的地位,詐
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
Thumbnail
  經過三篇的進展,我們目前實作的網路已經能做到同時訓練多種風格,且後續可以直接進行轉換,不用重新訓練,但是這種方法畢竟還是受到了預訓練的風格制約,無法跳脫出來,那麼有什麼辦法能夠讓他對於沒學過的風格也有一定的反應能力呢?
Thumbnail
上篇我們已經把風格融入在一個網路之中,實現了訓練一次就可以轉換不同的圖片成我們訓練的風格,但是這樣還不夠,因為這樣每個風格都得訓練一個網路來轉換,太浪費了,那麼,我們有沒有辦法在同一個網路中訓練多個風格呢?
這個世界有多少人,就有多少的觀點,多少的認知與解讀, 因此,失敗是正常的,被拒絕是正常的; 當我們成功了、被認可了, 我們會感激,並且珍惜種種的機緣, 當我們用這樣的角度與態度看待人事物,就不會那麼糾結著要完美了。
上一篇文章提到有些介面不應被繼承,但物件導向的子類別只能繼承父類別的介面,因而產生一些不合適的介面實作。trait/typeclass則沒有這種繼承機制,這似乎使需要繼承的特性無法直接使用。然而函數式導向比起繼承,更適合使用組合,根本不需要使用繼承疊加特性。 資料類型的定義往往跟怎麼建構模型相
Thumbnail
在創作的路上真的很多人問我說 到底要怎麼做出符合自己期待 但又可以表現得很有美感的作品?🥹 這個問題真的應該是每個創作者都一直在學習的課題吧!
提問的內容越是清晰,強者、聰明人越能在短時間內做判斷、給出精準的建議,他們會對你產生「好印象」,認定你是「積極」的人,有機會、好人脈會不自覺地想引薦給你
Thumbnail
在重新撰寫《重構命運》中涉及雙生火焰和不同靈魂伴侶關係的篇章之前,我和本源/高我聊了聊這些年我對此的一些心得和困惑,集結成一篇QA集供有興趣的讀者參考,近期可能也會隨興發其它議題統整的QA集。
Thumbnail
羽毛螢石被譽為智慧之石,主要成分為氟化鈣與矽,常呈現紫、綠、藍、黃等多種顏色。它不僅以其美麗的透明度和光澤受到喜愛,還能增強思維清晰度,適合學生和創意工作者佩戴。此篇文章探討羽毛螢石的特點、適用人群及其對於靈性提升的幫助,以及不同顏色螢石的獨特功效,讓你瞭解如何善用此寶石以提高學習和工作效率。
Thumbnail
和匱乏比起來,我們較難因應豐盛。凡事順遂的時候要嚴守紀律,在心理上拋去身外之物十分困難,但這正是一個人最需要紀律的時候。 建立一個系統,其中不管誰倒下去,都不會拖累其他人,持續不斷的失敗,有助於保存整個系統。 為了穩定而設法取得穩定,在經濟和外交上是一種冤大頭遊戲。(銀行大到不能倒的地位,詐
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
Thumbnail
  經過三篇的進展,我們目前實作的網路已經能做到同時訓練多種風格,且後續可以直接進行轉換,不用重新訓練,但是這種方法畢竟還是受到了預訓練的風格制約,無法跳脫出來,那麼有什麼辦法能夠讓他對於沒學過的風格也有一定的反應能力呢?
Thumbnail
上篇我們已經把風格融入在一個網路之中,實現了訓練一次就可以轉換不同的圖片成我們訓練的風格,但是這樣還不夠,因為這樣每個風格都得訓練一個網路來轉換,太浪費了,那麼,我們有沒有辦法在同一個網路中訓練多個風格呢?
這個世界有多少人,就有多少的觀點,多少的認知與解讀, 因此,失敗是正常的,被拒絕是正常的; 當我們成功了、被認可了, 我們會感激,並且珍惜種種的機緣, 當我們用這樣的角度與態度看待人事物,就不會那麼糾結著要完美了。
上一篇文章提到有些介面不應被繼承,但物件導向的子類別只能繼承父類別的介面,因而產生一些不合適的介面實作。trait/typeclass則沒有這種繼承機制,這似乎使需要繼承的特性無法直接使用。然而函數式導向比起繼承,更適合使用組合,根本不需要使用繼承疊加特性。 資料類型的定義往往跟怎麼建構模型相