特性系統的技能樹

更新於 2024/01/20閱讀時間約 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
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
最近在一場演講中,談到職場工作者如何透過學習來成長?我以這張個人知識系統的運作機制來說明,包含觀點、工具與經驗三種元素。
Thumbnail
V型反轉機率是非常低的上面我把自1999年起至今20年間的加權日線走勢的空頭V型反轉擷取出來,總共只有13次,一年不到一次⋯⋯
Thumbnail
V型反轉   重要均線運動模式的最後一種,就是交易者的夢魘,V型反轉,我們一樣要先來看他的定義。注意我前面是說「交易者」,亦即不只是「均線系統交易者」,而是包含使用任何指標、系統的「順勢交易者」,你說那猜頭摸底搶反彈拉回的逆勢交易者呢,是不是就比較吃香了?錯,即便是逆勢交易或區間系統的交易者,V轉
Thumbnail
                                                V型反轉機率是非常低的 上面我把自1999年起至今20年間的加權日線走
Thumbnail
                                                V型反轉機率是非常低的 上面我把自1999年起至今20年間的加權日線走
Thumbnail
V型反轉   重要均線運動模式的最後一種,就是交易者的夢魘,V型反轉,我們一樣要先來看他的定義。注意我前面是說「交易者」,亦即不只是「均線系統交易者」,而是包含使用任何指標、系統的「順勢交易者」,
Thumbnail
V型反轉   重要均線運動模式的最後一種,就是交易者的夢魘,V型反轉,我們一樣要先來看他的定義。注意我前面是說「交易者」,亦即不只是「均線系統交易者」,而是包含使用任何指標、系統的「順勢交易者」,
Thumbnail
延續上週多(空)頭排列不作空(多)的交易誡律,我們今天要先再進一步談一個重要的交易觀念,俗稱「魚頭魚尾不要吃」,也就是不要摸頭摸底,猜轉折。   所謂頭尾就是波段走勢的高低點或長期趨勢的反轉點,一般
Thumbnail
兩點提醒大家,首先是我很早以前也說過的,就是很多伙伴對於我之前在股板PO的文章都很熟悉了,最主要的包括台指期標的文和交易系統系列文,不過我當初都是一時興起寫的,內容鬆散,也不精確,更不是我所有的知識
從今天開始一直到課程結束的內容,就是我的交易系統最高階也是最精華的部分,以我們一直在用的比喻來說,就是大三大四的課程,也是份量最重,重要性最高的,在進入正課之前,我有些話想先跟各位說。   人生在世
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
最近在一場演講中,談到職場工作者如何透過學習來成長?我以這張個人知識系統的運作機制來說明,包含觀點、工具與經驗三種元素。
Thumbnail
V型反轉機率是非常低的上面我把自1999年起至今20年間的加權日線走勢的空頭V型反轉擷取出來,總共只有13次,一年不到一次⋯⋯
Thumbnail
V型反轉   重要均線運動模式的最後一種,就是交易者的夢魘,V型反轉,我們一樣要先來看他的定義。注意我前面是說「交易者」,亦即不只是「均線系統交易者」,而是包含使用任何指標、系統的「順勢交易者」,你說那猜頭摸底搶反彈拉回的逆勢交易者呢,是不是就比較吃香了?錯,即便是逆勢交易或區間系統的交易者,V轉
Thumbnail
                                                V型反轉機率是非常低的 上面我把自1999年起至今20年間的加權日線走
Thumbnail
                                                V型反轉機率是非常低的 上面我把自1999年起至今20年間的加權日線走
Thumbnail
V型反轉   重要均線運動模式的最後一種,就是交易者的夢魘,V型反轉,我們一樣要先來看他的定義。注意我前面是說「交易者」,亦即不只是「均線系統交易者」,而是包含使用任何指標、系統的「順勢交易者」,
Thumbnail
V型反轉   重要均線運動模式的最後一種,就是交易者的夢魘,V型反轉,我們一樣要先來看他的定義。注意我前面是說「交易者」,亦即不只是「均線系統交易者」,而是包含使用任何指標、系統的「順勢交易者」,
Thumbnail
延續上週多(空)頭排列不作空(多)的交易誡律,我們今天要先再進一步談一個重要的交易觀念,俗稱「魚頭魚尾不要吃」,也就是不要摸頭摸底,猜轉折。   所謂頭尾就是波段走勢的高低點或長期趨勢的反轉點,一般
Thumbnail
兩點提醒大家,首先是我很早以前也說過的,就是很多伙伴對於我之前在股板PO的文章都很熟悉了,最主要的包括台指期標的文和交易系統系列文,不過我當初都是一時興起寫的,內容鬆散,也不精確,更不是我所有的知識
從今天開始一直到課程結束的內容,就是我的交易系統最高階也是最精華的部分,以我們一直在用的比喻來說,就是大三大四的課程,也是份量最重,重要性最高的,在進入正課之前,我有些話想先跟各位說。   人生在世