同調的泛型

閱讀時間約 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做到。事實上並不常需要丟掉類型資訊(在物件導向常常用到只是因為它的設計比較容易這麼使用),而且大多時候仍可以透過自己寫加法類型手動把類型資訊帶到執行時期。

3會員
23內容數
這不是教你如何從物件導向到函數式編程的入門教程。我會深入探討物件導向與函數式編程的差異,並討論為什麼你應該使用函數式編程並徹底放棄物件導向。
留言0
查看全部
發表第一個留言支持創作者!
have bear的沙龍 的其他內容
物件導向是什麼
閱讀時間約 3 分鐘
少用繼承,多用介面
閱讀時間約 6 分鐘
定義資料類型而非類別
閱讀時間約 10 分鐘
悅耳的類型系統
閱讀時間約 11 分鐘
函式的正確用法
閱讀時間約 7 分鐘
函式的類型
閱讀時間約 8 分鐘
你可能也想看
兒童SEL-遊戲化的情緒調節法(1)你家的孩子, 情緒屬於<刺蝟型>?還是屬於<悶葫蘆型>? 相信無論是哪一型, 對於家長都充滿了挑戰, 對吧 ! 你說這有沒有解套的方法呀 ? 有的 ! 帶孩子一起透過遊戲化的情境, 帶孩子學習兒童SEL吧 !
Thumbnail
avatar
孫子玲
2023-10-02
延續母乳保護力、調整體質的兒童營養好物-孕哺兒日本乳鐵蛋白 #延續母乳保護力、調整體質的兒童營養好物-孕哺兒日本乳鐵蛋白 https://momtobe.cc/cing9 面臨家有幼兒園、從小體質敏感又常常不舒服請假的孩子著實讓我頭痛萬分,但自從讓晴晴持續補充了孕哺兒的 #兒童營養品 後,漸漸的她的體質也慢慢的改變了,這讓我更信賴孕哺兒了。 #孕哺兒
Thumbnail
avatar
家有二寶-晴晴&程程
2023-10-02
【諮商理論】「同調--互補-平衡」的循環三階段在諮商關係的角色互動裡,我的塑造比例與先後是: 1.建立關係與同理需要「同調」 2.從「互補」面找新的契機 3.整合進去,讓案主有「新的平衡」
Thumbnail
avatar
林仁廷心理師
2023-09-22
文學|不與時人彈同調的莊嚴氣派|首獎的留白與最後的留白|雨聲裡偏遠的哭聲|你不回來吃飯|黃國峻「一年中會有一兩天,天空下起份量極重的雨水,因為只有那一兩天,地球的角度剛好讓冷空氣和熱對流形成一道水門,只要偏差一點,水門就不存在。」~黃國峻《 水門的洞口》
Thumbnail
avatar
LAZY LAZY BONE懶懶骨頭的裸辭日記
2023-08-10
溶不掉的緣 /MHYK同人日本手遊《魔法使的約定》同人作品 / 因緣主向 學園paro / 4/28日新增登入短故事相關 / 蹭一下雙子梗 單純的吃雪糕日常?
avatar
魚乾荳荳媽
2023-04-28
祖孫情-我把水餃丟掉了_之同長的重要「今天大家很開心的分享昨晚帶回家的水餃,同學都說好吃、很開心,而Chace失落的說阿嬤把水餃丟掉了。」 聽完老師這麼說,我才驚覺Chace這麼放在心上,並且是失落的,但卻沒與我跟阿嬤表達。 當晚回家後,我立馬跟阿嬤說這件事,阿嬤也很不好意思的跟Chace道歉,並說明昨晚帶回家,Chace沒有即時說有
Thumbnail
avatar
橘子汽水
2023-01-14
【劃掉待辦事項的同時,是否也劃掉了熱情?】當每件事都是妳真心渴望想做的,妳自然就會抽出時間去做。比起管理時間,更重要的是找到人生中的北極星,確立方向後,就知道該怎麼走。清單上那些超出負荷的待辦事項,只是過度努力的證明,而不是做事多有效率。 仔細想想,確實是如此。就像寫作這件事,因為是真心渴望想做的,就算再忙也會抽出時間寫作,透過文字重新梳理
Thumbnail
avatar
和你一樣都是媽
2022-10-24
看穿表皮的 Michelson diagnostics-光學同調斷層掃描技術在美國每年約有 5 百萬人罹患皮膚癌,其中以基底細胞癌( Basal cell carcinoma,BCC )和鱗狀細胞癌( Squamous cell carcinoma,SCC )居多,而黑色素瘤( melanoma )雖只佔 1%的比例,但致死率極高。
avatar
解鎖生技
2021-12-03
18.蒸發掉的伊琳同學十八歲打死都不願吃維A的發雞盲少女專門跟母親對著幹。13號瞪著叫伊琳的少女。 13號:「你想把母親活活的氣死還是真心的跟自己雙眼過不去呢?」 伊琳:「⋯⋯她說今天要去跟她丈夫簽離婚協議書嘛!我不是已經坐在你面前聽帥哥您的辱駡嘛!」 13號:「那你到底有沒有吃我上次給你的維生素呢?我的小美女。」 伊琳
avatar
尚盧之劍
2019-12-01
老頑童的調皮懺悔---《傑克蓋的房子》如果我們認同「懺悔」是一種崇高人性的展現,我們就不該否定「傑克蓋的房子」有其正面意義,更何況這還不是那種聲淚俱下、叫苦連天似的懺悔,而是唱作俱佳、普天同慶的懺悔,150分鐘,拉斯馮提爾教給了我們,原來還有這種形式的懺悔。
Thumbnail
avatar
Lizard
2018-12-04