同調的泛型

更新於 發佈於 閱讀時間約 7 分鐘

前幾篇文章討論了類型系統的合理性,而這會影響我們對於變數與函式是什麼的理解。其中泛型是當中很重要的一個元素,很多討論都是基於泛型的使用。泛型會大大地增加類型系統的複雜度,因此有些語言選擇不提供泛型(go),但缺少泛型又會使簡單的容器都無法用類型精確描述。泛型的強大必須結合有紀律的類型系統才能顯現,但不能做動態類型檢查的類型系統必須有泛型,否則就連最簡單的抽象化都做不到。這顯示了泛型本質上與抽象化緊密相連,而且使用抽象化的唯一方法就是泛型。在這種類型系統,類型描述的是實際的資料結構,而抽象化的任務全部都交給泛型完成,不像物件導向類別同時可以描述抽象的物件。


「抽象化」是將某些結構或概念從實際案例中抽取出來,並讓它可以應用在未知的情境下,而泛型就是量化未知的方法。例如我們有 fn swapIntAndString(Tuple<int,string>)->Tuple<string,int> 和 fn swapFloatAndBool(Tuple<float,bool>)->Tuple<bool,float>等函式,即使它們的類型都不一樣,但實作看起來都一樣。它們都是把tuple的兩個元素做交換,因此可以將這種結構抽取成泛型函式。就像把相同的程式碼抽取出來時,需要將不同的部分改寫成參數,抽取泛型函式時也需要將不同的類型改寫成類型變數。例如:fn swap<T,S>(Tuple<T,S>)->Tuple<S,T>,其中T和S是類型參數。抽取函數可以把相同的運算邏輯獨立出來,而使用泛型可以進一步把無關的資訊排除,例如交換Tuple的元素不需要知道內容是什麼,因此我們可以把這部分的資訊取代成一個佔位符。在像是c, go這種沒有泛型的程式語言是做不到這種抽象化的,只能藉由類型轉換自己判斷應該是什麼。


泛型函式需要輸入變數與類型作為參數,而參數的類型跟類型參數通常都是有關的,因此編譯器大多時候都能自動推斷類型參數。這似乎代表類型與變數是同等的存在,但事實上在大多數靜態類型程式語言不是如此,通常類型只會出現在編譯時期,類型的值不會受到變數影響,這代表類型與變數是兩個世界。參數可以看作表達式的佔位符,因此類型參數也能看作函式類型表達式的佔位符,而像是Tuple<A,B>, fn(A)->B等類型建構子則可以看作類型的函式:它們接受兩個類型並回傳類型。這種函式和一般的函式還是差了一大截,因為F<A,B>在所有類型之中都是唯一的,不可能有其他的表達式能表示相同的類型,這就是為什麼他稱做類型建構子而非類型函式(在typescript中則真的是一種函式了)。這種特性使得類型看起來是什麼就是什麼,而泛型真的就像是類型的範本一樣一目了然。


如果函式的操作與變數的類型或是資料結構的某個部分類型無關,那麼這個類型就能抽取出來變成類型變數。就算你的情境並不需要這麼做,也應該要嘗試使用泛型,因為這麼做可以表示這部分的邏輯與這個類型無關。相較於直接把類型取代成void pointer/empty interface/any type,泛型把未知的類型參數化,使得變數之間的聯繫變得清楚,更能描述整體結構,這對容器相關的資料結構特別重要。當泛型函式關係到多個未知的類型時,類型變數可以把相關的未知類型形成同調,讓不同的「未知」能夠跨越多個函式而不會混在一起。這就像原本作為範本上的「洞」的類型變數,跨越整個計算過程形成「隧道」,而這些隧道編織出的形狀描述了這個計算過程的整體結構。透過free theorem,我們甚至能直接推斷出這些隧道大概長怎樣。


物件導向用子類關係把無關的資訊隱藏起來,而泛型透過參數化把無關的資訊抽離,它們的差別在於抹除資訊的方法,泛型的做法保留了同調性。利用同樣的概念可以把物件導向的繼承機制用泛型模擬出來。PureScript擁有一種無序的乘法資料結構Record,這種結構能夠擴展成員變數,這很像TypeScript的Record,但他具有更健全的語義。例如 { a :: String, b :: Boolean } 代表一個帶有a, b成員的物件,但不能有其他成員,這一點跟TypeScript很不一樣。TypeScript的Record只告訴你可以存取哪些成員,但它可能有其他成員。從物件導向的角度來看,擁有其他成員的物件可以看作這個類型的子類,也就是它同時包含了這個類型的物件實體和其子類的物件實體,因此他更像抽象類別/介面而非資料類型。PureScript的Record類型明確描述所有的成員,若要像TypeScript只關注某些成員的存取,可以透過泛型把未知的部分參數化(row polymorphism),寫成 { a :: String, b :: Boolean | r },其中r是特殊的類型變數,它代表所有其他的成員。這與TypeScript的差別在於它不會丟掉任何資訊,例如函式 increase :: { counter :: Number | r } -> { counter :: Number | r },使用這個函式不會丟任何類型資訊。在TypeScript要達到相同效果必須使用帶有約束的泛型函式 <T extends {id:Number}>(val: T) => T。反之,在PureScript要像TypeScript丟掉類型資訊就必須使用quantifier,寫成 exist r. { counter :: Number | r }(事實上PureScript沒有exist,但可以使用forall做出來)。


定義函式時應盡量不要丟掉類型資訊,因為這樣很容易忽略未考慮的狀況,而類別的方法一般都是在這樣的情況下定義的函式,因此應該少用方法。例如在為類別定義相等方法時一般寫作:equals(other: ThisClass): boolean,然而當遇到繼承它的子類物件實體時該怎麼辦?很明顯子類的新成員變數也應該一起比較,而且比較不同類別的實體是沒有意義的,因此這個方法本質上就是不可繼承的。若是寫成泛型函式問題就很明顯:function equals<T extends ThisClass>(a: T, b: T): boolean,這個函式看起來就不可能實作,因為你不知道類型T是什麼是要怎麼比較。如果你真的把所有子類的判斷都寫死在裡面,就違反了依賴抽象的規則,這很容易出錯,除非你能確定不會有其他類別繼承ThisClass,不過這時你應該嘗試使用加法類型(至少在TypeScript是可以這麼做的)。物件導向的變數類型宣告預設會丟掉實體類型的資訊,使得在實作時必須使用動態型別檢查(或是自己檢查),這讓類型資訊不能順暢地流動,從而產生更多的錯誤機會。而使用泛型雖然會讓變數變多,因而產生太多雜音影響清晰度,但類型得以串聯起來,讓編譯器能為你檢查錯誤。物件導向傾向在執行時期使用類型,而函數式編程一般只能在編譯時期使用類型,好處是這麼做可以在編譯時期就檢查出錯誤。然而有時仍需要丟掉類型資訊,或是把類型資訊下放到執行時期(為了減少雜音或是聯集不同類型),而這對大部分函數式程式語言來說不可能做到或是很難用。例如rust提供了trait object把類型資訊丟掉,並提供Any trait在執行時期存取類型資訊;Haskell或是PureScript可以使用forall把類型資訊隱藏起來,但沒辦法在執行時期存取類型資訊,Idris2則可以使用Sigma type做到。事實上並不常需要丟掉類型資訊(在物件導向常常用到只是因為它的設計比較容易這麼使用),而且大多時候仍可以透過自己寫加法類型手動把類型資訊帶到執行時期。

avatar-img
4會員
28內容數
這不是教你如何從物件導向到函數式編程的入門教程。我會深入探討物件導向與函數式編程的差異,並討論為什麼你應該使用函數式編程並徹底放棄物件導向。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
have bear的沙龍 的其他內容
函式跟資料結構一樣都有類型,它不只是特定於函式的概念,而是跟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的差異。這篇
在現代,物件導向雖然仍是主流,函數式慢慢得到關注。物件導向並不適合所有的程式邏輯,但在像是Java的物件導向的程式語言中,幾乎所有值都被當作物件,因此在一些情境下Java寫起來會非常冗余。物件導向流行的原因大概是因為它的思考方式比較符合我們對於世界的認知,但邏輯推理與解決問題的方式卻不一定符合我們的
函式跟資料結構一樣都有類型,它不只是特定於函式的概念,而是跟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的差異。這篇
在現代,物件導向雖然仍是主流,函數式慢慢得到關注。物件導向並不適合所有的程式邏輯,但在像是Java的物件導向的程式語言中,幾乎所有值都被當作物件,因此在一些情境下Java寫起來會非常冗余。物件導向流行的原因大概是因為它的思考方式比較符合我們對於世界的認知,但邏輯推理與解決問題的方式卻不一定符合我們的
你可能也想看
Google News 追蹤
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
想到日本旅遊就一定會聯想到日本沖繩這個漂亮的海島都市!AsiaYo 今天要帶來 5 間特色沖繩飯店,不只有鄰近海灘的度假酒店、適合家庭旅遊的親子友善飯店,還有隱身在山間的景觀飯店!這篇沖繩住宿攻略真的含金量超高😍還不趕緊收藏起來,今年去沖繩自由行才不會錯過這些超優質飯店!
Thumbnail
想到日本旅遊就一定會聯想到日本沖繩這個漂亮的海島都市!AsiaYo 今天要帶來 5 間特色沖繩飯店,不只有鄰近海灘的度假酒店、適合家庭旅遊的親子友善飯店,還有隱身在山間的景觀飯店!這篇沖繩住宿攻略真的含金量超高😍還不趕緊收藏起來,今年去沖繩自由行才不會錯過這些超優質飯店!
Thumbnail
和高中的朋友(女生)一起旅行。旅行途中和住在同一家飯店不認識的4個男生變得熟,並且在他們房間一起喝酒。我感覺到氣氛漸漸變得很危險。我完全沒喝酒就回房了,但朋友說她還要繼續喝。所以我把房間鑰匙給了朋友,感覺真的覺得很危險,所以我留了字條說去認識的人那邊睡,另外開了一間房間睡覺……
Thumbnail
1.0 從函數到函算語法 1.4 函算語法 1.4.1 語法範疇理論導論 1.4.2 函算語法與函數概念 三 弗雷格從語言結構的觀點出發,提出了函數可以被視為一個不完整的表式。如果我們將一個函數拆解為一個由一個函子及其 (一個或多個) 論元所組成的表式,那麼該函子便是一個有待滿足的
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
達爾葳妮飯店的一切都令人感到奇幻,不論是「三〇・五號」的住址,或是無限享用的甜點咖啡,抑或是三百六十五天全年都花朵盛開的杏樹,這裡彷彿與人間隔絕的仙境。然而,我們需要先知曉一件事情:煩惱是會被帶進來的。應該說,是「需要被帶進來的」。因為達爾葳妮飯店所擁有的,絕不是藉由短暫歡愉減緩煩悶的魔法。
※ OPP第三大核心-多型 ※ 多型的基本定義: 多型是利用繼承的特性,讓不同的子類別可以實現相同的介面,但在呼叫這些介面的方法時會表現出不同的行為。這使得程式設計更具彈性和擴展性,避免了複雜的條件判斷式,同時促進了代碼的重用。 class Animal { makeSound() {
多型性(polymorphism)是物件導向中的一個重要概念,它指的是同一個方法或函式在不同的物件類別中可以有不同的行為。在 Python 中,多型性通常是通過繼承和方法重寫(method overriding)來實現的。 主要是為了不同資料類型的實體提供統一的介面,我們藉由下面的程式範例來多理解
前幾篇文章討論了類型系統的合理性,而這會影響我們對於變數與函式是什麼的理解。其中泛型是當中很重要的一個元素,很多討論都是基於泛型的使用。泛型會大大地增加類型系統的複雜度,因此有些語言選擇不提供泛型(go),但缺少泛型又會使簡單的容器都無法用類型精確描述。泛型的強大必須結合有紀律的類型系統才能顯現,但
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
想到日本旅遊就一定會聯想到日本沖繩這個漂亮的海島都市!AsiaYo 今天要帶來 5 間特色沖繩飯店,不只有鄰近海灘的度假酒店、適合家庭旅遊的親子友善飯店,還有隱身在山間的景觀飯店!這篇沖繩住宿攻略真的含金量超高😍還不趕緊收藏起來,今年去沖繩自由行才不會錯過這些超優質飯店!
Thumbnail
想到日本旅遊就一定會聯想到日本沖繩這個漂亮的海島都市!AsiaYo 今天要帶來 5 間特色沖繩飯店,不只有鄰近海灘的度假酒店、適合家庭旅遊的親子友善飯店,還有隱身在山間的景觀飯店!這篇沖繩住宿攻略真的含金量超高😍還不趕緊收藏起來,今年去沖繩自由行才不會錯過這些超優質飯店!
Thumbnail
和高中的朋友(女生)一起旅行。旅行途中和住在同一家飯店不認識的4個男生變得熟,並且在他們房間一起喝酒。我感覺到氣氛漸漸變得很危險。我完全沒喝酒就回房了,但朋友說她還要繼續喝。所以我把房間鑰匙給了朋友,感覺真的覺得很危險,所以我留了字條說去認識的人那邊睡,另外開了一間房間睡覺……
Thumbnail
1.0 從函數到函算語法 1.4 函算語法 1.4.1 語法範疇理論導論 1.4.2 函算語法與函數概念 三 弗雷格從語言結構的觀點出發,提出了函數可以被視為一個不完整的表式。如果我們將一個函數拆解為一個由一個函子及其 (一個或多個) 論元所組成的表式,那麼該函子便是一個有待滿足的
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
達爾葳妮飯店的一切都令人感到奇幻,不論是「三〇・五號」的住址,或是無限享用的甜點咖啡,抑或是三百六十五天全年都花朵盛開的杏樹,這裡彷彿與人間隔絕的仙境。然而,我們需要先知曉一件事情:煩惱是會被帶進來的。應該說,是「需要被帶進來的」。因為達爾葳妮飯店所擁有的,絕不是藉由短暫歡愉減緩煩悶的魔法。
※ OPP第三大核心-多型 ※ 多型的基本定義: 多型是利用繼承的特性,讓不同的子類別可以實現相同的介面,但在呼叫這些介面的方法時會表現出不同的行為。這使得程式設計更具彈性和擴展性,避免了複雜的條件判斷式,同時促進了代碼的重用。 class Animal { makeSound() {
多型性(polymorphism)是物件導向中的一個重要概念,它指的是同一個方法或函式在不同的物件類別中可以有不同的行為。在 Python 中,多型性通常是通過繼承和方法重寫(method overriding)來實現的。 主要是為了不同資料類型的實體提供統一的介面,我們藉由下面的程式範例來多理解
前幾篇文章討論了類型系統的合理性,而這會影響我們對於變數與函式是什麼的理解。其中泛型是當中很重要的一個元素,很多討論都是基於泛型的使用。泛型會大大地增加類型系統的複雜度,因此有些語言選擇不提供泛型(go),但缺少泛型又會使簡單的容器都無法用類型精確描述。泛型的強大必須結合有紀律的類型系統才能顯現,但