可變性與參考

閱讀時間約 8 分鐘

函數式編程跟物件導向一個很大的差異在於對資料可變性(mutability)的態度,函數式編程不鼓勵修改原有的資料,有些語言甚至沒有修改的概念;而物件導向專注於狀態的改變,物件作為閉包就已經假設資料是可變的。這種對於可變性的態度注定物件導向比較容易得到關注,因為這個模型比較符合電腦底層的運作邏輯,而我們似乎比較擅長以物件思考。在這裡我們將可以修改的「東西」稱作物件,反之稱為資料。這種分法或許與其他語言有一些微妙的差異,但在這篇文章將如此指代。可變性跟參考、複製、相等性等概念有關,以下將分析它們如何互相影響。


可變性會影響我們對變數身份(identity)的認知。身份代表一種唯一的概念,當兩個變數代表同一個身份時,這時我們會說它們「參考」了同一個東西。參考就像指代某種特定東西的別名,使用這個參考就如同使用這個東西。如果不可能有多個變數參考同一個東西,那麼身份這個概念就沒有意義。具有相同內容的物件不能代表它們是同一個東西,還有一種內在的特性把它們區分開來。關鍵在於:當修改了一個變數內容,其他參考同一個東西的變數也會改變,因此具有身份的東西可以擁有可變性。更進一步地,具有可變性的東西一定需要唯一的身份,因為「修改」只有在會影響其它地方時才有意義,否則只要覆蓋(shadowing)原有的變數就能達到同樣的效果。例如修改區域變數的操作可以改成覆蓋區域變數,然而這有時會需要重構程式碼後才有辦法做到。尤其是遇到迴圈時,就需要將函式改寫成遞迴,這會大大地增加程式碼的複雜度,因此比較寬鬆的函數式程式語言會允許使用可變的區域變數。


身份是可變性的必要條件,可變的物件必定具有唯一的身份。相反地,身份對不可變的資料沒有意義,畢竟如果它的內容不能改變,也就不用在意會不會因為參考同一個東西而被其他操作影響。更嚴格地,我們甚至不允許比較變數是否參考同一個東西,因此不可變的資料本質上是沒有身份的,我們沒辦法以此作為資料結構的一部分。這個限制是來自參考透明性(referential transparency),它要求參考不能有意義,甚至不能比較參考的位址。對於Haskell這種嚴格遵守參考透明性的純函數式程式語言,資料結構的正確性不能依賴於參考,甚至不能藉由比較參考位址加速比較的操作。參考透明性使得純函數式編程非常數學,畢竟數學物件本來就沒有可變的概念。反過來說,函數式編程的不可變特性就源自這個原則,但事實上大部分的函數式編程都只有著重在不可變的特性上,並不特別在意參考透明性。需要強調的是「不可變」的定義其實非常嚴格,不可變的資料只能包含不可變的成員,不能包含任何可變的參考,否則它就不是真正不可變的。然而很多程式語言的不可變特性都只有一層,例如JavaScript的const arr = [1,2,3]仍然可以修改陣列內容,Python的tuple是不可變的,但([1],[2])的內部列表仍是可變的。ocaml雖然標榜以不可變為主,但仍然能夠在資料結構裡修改標示為可變的成員,因此嚴格來說它也不是不可變的。而rust則使用borrowing rule讓共享參考的不可變特性傳遞到底下所有成員,但仍然可以藉由Cell, RefCell等具有內部可變性的結構打破規則。


如果不可變的資料只能包含不可變的參考,那麽把這個參考取代成相同內容但不同身份的參考並不會有什麼問題,當然反過來也是可以的。然而參考對於不可變的資料仍有一些非常微妙的作用。就算是非常看重參考透明性的Haskell,有時候我們仍必須打破規則使用不符合規則的方法除錯,例如利用trace等方法印出訊息,這樣就不用為了除錯而把所有東西都改寫成Monad。然而這時理解不可變的參考就特別重要,尤其是對於Haskell這種惰性求值的程式語言,而且參考的概念對於優化惰性求值的效能來說非常重要。因此即使不可變的參考不會影響資料的正確性,仍是非常重要的概念。如果不可變的資料包含指向自我的參考,問題就變得複雜了。一般來說這種結構是沒辦法構造出來的,但對於惰性求值的Haskell來說是家常便飯。這種結構代表了遞迴的資料結構,把它展開會得到無窮大的資料。把參考視覺化來看,它形成了有環有向圖的資料結構。你如果想要藉由檢查參考位址判斷是否是同一個節點,就必須打破參考透明性,對於Haskell來說這不是個好方法,因此這種結構在Haskell並不是如此應用的。在Haskell,只有在需要無窮大的資料時才會使用這種自我參考構造資料,因此不應該把參考當作資料結構的一部分來理解它,它只是「剛好」共享參考而已。


可變的物件因為隨時會被修改,因此當需要描述某些獨立的資訊時就需要複製。更麻煩的是當他由好幾層可變的物件組成時,例如包含可變列表的物件,這時就必須深度複製所有的東西。使用像是JavaScript/TypeScript這種物件導向的程式語言時,又想在它上面用函數式的風格寫程式,就會需要做一堆複製,因為幾乎所有東西都沒有辦法保證是不變的。就算是只有readonly成員的物件也不是不可變的,這只代表你不能改變它,而不是它不會改變。最好的方法是用真正不可變的資料描述資訊,如此一來就不用煩惱複製的問題了。複製本身並不一定是簡單自然的,尤其是牽涉到參考時。有時我們會希望保留參考不深度複製,例如複製以參考表示的圖的節點時,參考位址描述了節點的身份,這時複製就必須考慮到參考相等性。但對於參考本身無意義的資料,自動實作複製是非常自然的,或是可以直接引用相同的資料,畢竟它不可能被修改。Java, JavaScript等語言預設結構是可變的且只會複製參考,但又缺少簡單複製結構和比較結構的方法。這些限制使得我們難以利用資料來思考,因而傾向利用可變的物件和參考構造模型。像是Haskell, rust則是利用巨集輔助定義一些資料應有的特性。


在Java等物件導向的程式語言中,原始類型與物件都具有預設的相等運算子,但它們比較的東西是不同的:原始類型因為是不可變的,因此比較的是數值內容,而物件則比較身份,也就是參考位址是否相等。相等性(equality)固化了我們對變數類型的思考方式,也就是我們應該要把它們當成資料還是物件,而這種選擇顯示了Java想要以物件為主但又必須使用原始類型進行基本運算的矛盾。ocaml則擁有結構相等與參考相等兩種,結構相等性比較定義的資料結構是否相等,而參考相等性則比較身份。但因為ocaml會對不可變的資料做一些優化,因此參考相等性在資料上會有一些奇怪的結果。參考相等性可以顯現物件的身份,然而這並不是必要的。如果說要讓資料擁有可比較的身份,只要在他上面附加一個唯一的標示符就能做到,並不一定要由位址決定,這顯示了參考相等性不是身份的必要條件。相等性可以用來顯現這個類型代表什麼,資料實質意義上的相等有時並不代表結構相等。例如利用二元搜尋樹實作的無序集合會根據插入順序不同而有不同的結構,而它們仍會被當作相同的資料,因為它代表的不是二元搜尋樹而是無序集合。而比較圖的相等性不是簡單的操作,它需要計算複雜性非常高的演算法。程序式編程常常以相等性判斷數值,並以此進行流程控制,這似乎代表相等性是非常重要的操作。然而對於函數式程式語言,流程控制是基於模式比對完成。這種模式比對是根據結構控制流程,而這只對應了結構相等一種,其它的相等性對於流程控制並沒有直接的聯繫。因此相等性不應被當作理所當然的運算,它其實只是具有某種特性的二元關係而已。


流程控制在程序式編程與函數式編程之間有很大的差異,不只是因為對於相等性的使用與否,可變性的差別也是原因。程序式編程的流程控制依賴於可變性,例如if-else陳述式常常會通過修改已有的變數把每個分支的結果帶到這個區塊後面,這是因為分支的區域變數是沒辦法直接被外面使用的。而迴圈基本上只能在有可變的變數下運作,同樣地,迴圈內的區域變數只能藉由修改外部變數帶出來。而rust則把if-else和loop當成表達式,讓它們可以「回傳」結果給外部使用,這非常像函數式編程。這種回傳結果而非修改變數的風格使得方法得以串聯起來,其中iterator很常使用這種方式,這比起使用迴圈易讀許多。純函數式程式語言因為不能修改變數,因此這種基於修改的流程控制是不可能實現的。對於流程控制的差異使得函數式編程與程序式編程的思考方式有很大的不同,而函數式的思考方式很多時候對於可變的情境仍然是非常有用的,因此混合風格的編程方式才能在現今展露頭角。


avatar-img
4會員
28內容數
這不是教你如何從物件導向到函數式編程的入門教程。我會深入探討物件導向與函數式編程的差異,並討論為什麼你應該使用函數式編程並徹底放棄物件導向。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
have bear的沙龍 的其他內容
這個系列的文章主要專注於物件導向到函數式編程的差異與分析,並針對概念與機制上的不同進行比較。很多人說物件導向和函數式編程沒有哪個比較好的問題,只有哪個比較適合的問題,然而我並不這麼認為,我透過這一系列的文章從各個角度討論它們之間的優缺點就是為了闡述我的觀點。物件導向錯在沒有理論基礎,但它贏在熟悉性,
前一篇文章中所提的函數式的三個機制明確說明了它關注的規則與能力具體是什麼。然而這套對於函數式編程的定義主要基於特定的類型系統,作為一個編程範式來說過於狹隘(物件導向的定義也是這樣)。更廣義的,我認為函數式編程主要依循三個原則,它們可以應用於任何程式語言,就算沒有靜態類型系統的支援也可以。例如在不管類
前面談了那麽多函數式編程與物件導向的差異,但我們還沒定義函數式編程。就像物件導向,函數式編程沒有明確的定義,每個人對於什麼是函數式編程都有不同的看法。在這裡我會總結前面的討論,給出我對於函數式編程的觀點。 物件導向注重封裝與延展性,因此一般基於三個機制:繼承、多型、封裝,它們代表了物件導向所重
說到物件導向就必須提五個原則,統稱SOLID,它被認為是物件導向的重要概念。這五個原則並不只適用於物件導向,事實上它很像函數式編程的習慣。它的命名很奇怪且容易讓人混淆,所以我會用我自己的翻譯解釋。 Single Responsibility Principle 是「單一職責原則」,認為一個模組
讓我在這篇文章總結一下前面對物件導向設計的討論,我們討論了物件導向的四個特性:繼承、抽象、多型、封裝,分析了它們的問題,並跟函數式編程的思維做比較。我們引入了與之相對應的特性:泛型、特性系統、模組化,有些特性雖然跟那四個特性很像,但在一些細微的地方有不同的詮釋,使得整體思考方式很不一樣。 「繼
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相
這個系列的文章主要專注於物件導向到函數式編程的差異與分析,並針對概念與機制上的不同進行比較。很多人說物件導向和函數式編程沒有哪個比較好的問題,只有哪個比較適合的問題,然而我並不這麼認為,我透過這一系列的文章從各個角度討論它們之間的優缺點就是為了闡述我的觀點。物件導向錯在沒有理論基礎,但它贏在熟悉性,
前一篇文章中所提的函數式的三個機制明確說明了它關注的規則與能力具體是什麼。然而這套對於函數式編程的定義主要基於特定的類型系統,作為一個編程範式來說過於狹隘(物件導向的定義也是這樣)。更廣義的,我認為函數式編程主要依循三個原則,它們可以應用於任何程式語言,就算沒有靜態類型系統的支援也可以。例如在不管類
前面談了那麽多函數式編程與物件導向的差異,但我們還沒定義函數式編程。就像物件導向,函數式編程沒有明確的定義,每個人對於什麼是函數式編程都有不同的看法。在這裡我會總結前面的討論,給出我對於函數式編程的觀點。 物件導向注重封裝與延展性,因此一般基於三個機制:繼承、多型、封裝,它們代表了物件導向所重
說到物件導向就必須提五個原則,統稱SOLID,它被認為是物件導向的重要概念。這五個原則並不只適用於物件導向,事實上它很像函數式編程的習慣。它的命名很奇怪且容易讓人混淆,所以我會用我自己的翻譯解釋。 Single Responsibility Principle 是「單一職責原則」,認為一個模組
讓我在這篇文章總結一下前面對物件導向設計的討論,我們討論了物件導向的四個特性:繼承、抽象、多型、封裝,分析了它們的問題,並跟函數式編程的思維做比較。我們引入了與之相對應的特性:泛型、特性系統、模組化,有些特性雖然跟那四個特性很像,但在一些細微的地方有不同的詮釋,使得整體思考方式很不一樣。 「繼
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
TriumphFX 伊斯兰账户有两种可供选择:可变账户和白金账户,两者有何区别以及如何选择?
Thumbnail
先須知識: 了解什麼是變數 作用域scope 在Rust中,變數預設是不可變的 let name = "Zoe"; // 預設變數不可變 也就同於Javascript中的const,若想讓變數可變則需要在let後面加上mut關鍵字: let mut name = "Vicky"; nam
Thumbnail
1)描述你的問題? 慣性出軌的修復可能性與如何看見自己可改變的地方 2)發生什麼事?10年戀愛+10年婚姻過程中先生都有幾次出軌狀況,他自己也不知道為什麼會這樣<去年他曾去諮商幾次無果>過往他會第一時間信誓旦旦說選擇家庭我也採相信他,讓他去處理外面現在才意識到沒有改善關係中真正的問題.
Thumbnail
這次的篇章主要在介紹狀態的可變性,透過約束來限制狀態,避免隨意更改狀態導致錯誤的合約出現,如果對於Solidity開發有興趣的朋友不妨參考「📚 更多關於Solidity的文章請看這裡…」,讓我們一起動動手學習開發智能合約吧! 我們都知道狀態在智能合約中扮演著非常重要的角色,經過什麼事件之後變化為什
Thumbnail
今天做了一個長長的夢 一開始是一個家庭的父親,突然身體自爆整個身體炸裂開來,場面讓人覺得觸目驚心,家庭成員有父親、母親、姐姐19歲、大弟17歲、二弟6歲、三妹4歲。 畫面一轉母親變身成為一隻白色貂,但是身形又像貂又像狐狸,不斷穿梭在大街小巷以及樹叢中繞路回家,路上一名帶著鴨舌帽的男人察覺到了!
談到少數族群,多數人會先聯想到原住民、新住民等族群 似乎大多數人是屬於多數的族群 事實上多數和少數是相對而言的 多數和少數也是可變的 更甚者,每個人都永遠是屬於少數的孤獨個體
Thumbnail
在思考「島嶼錨固」方案【註 1】時,我曾碰到一個問題:離岸多遠處才能捕捉到黑潮動能 ? 但,由於我沒有現場親眼看過黑潮,不敢保證上述說法是否屬實,希望日後能有機會驗證。 下列網址點進去也都能看到類似的圖片:
1、註冊地位於香港 由於中文貴金屬市場起步慢,在相關法律、監管機構欠缺的情況下,通常會有不良平臺鑽空子,以炒現貨貴金屬的旗號坑騙投資者。所以好的貴金屬交易平臺應該是怎樣的? 通常資質正規的平臺,其公司註冊地多坐落於香港繁榮地段,這是因為香港是老牌國際金融中心,貴金屬市場早已在當地紮根多年,擁有完善的
Thumbnail
關於標題〈可讀‧性〉,用字與模糊化的呈現方式,將在過去那些保守傳統的時代中,性與女性、同性等不可明言、不可言說、不被看見的狀態呈現出來了,那時候她/他們只能在渺茫曖昧的黑霧中摸索猜測,並且在逐漸明朗的現在讓文字清晰化,那些被汙名的過去,終於准許被寫出來,終於可讀(allow to read)。
Thumbnail
構造簡單,引擎從低速到高速作用圓滑。文氏管處之空氣速度幾乎保持一定,吸入空氣量隨真空及文氏管的開口面積而改變。高速時文氏管的開度大,進氣阻力小,容積效率高;低速時文氏管的空氣流速快,作用良好。
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
TriumphFX 伊斯兰账户有两种可供选择:可变账户和白金账户,两者有何区别以及如何选择?
Thumbnail
先須知識: 了解什麼是變數 作用域scope 在Rust中,變數預設是不可變的 let name = "Zoe"; // 預設變數不可變 也就同於Javascript中的const,若想讓變數可變則需要在let後面加上mut關鍵字: let mut name = "Vicky"; nam
Thumbnail
1)描述你的問題? 慣性出軌的修復可能性與如何看見自己可改變的地方 2)發生什麼事?10年戀愛+10年婚姻過程中先生都有幾次出軌狀況,他自己也不知道為什麼會這樣<去年他曾去諮商幾次無果>過往他會第一時間信誓旦旦說選擇家庭我也採相信他,讓他去處理外面現在才意識到沒有改善關係中真正的問題.
Thumbnail
這次的篇章主要在介紹狀態的可變性,透過約束來限制狀態,避免隨意更改狀態導致錯誤的合約出現,如果對於Solidity開發有興趣的朋友不妨參考「📚 更多關於Solidity的文章請看這裡…」,讓我們一起動動手學習開發智能合約吧! 我們都知道狀態在智能合約中扮演著非常重要的角色,經過什麼事件之後變化為什
Thumbnail
今天做了一個長長的夢 一開始是一個家庭的父親,突然身體自爆整個身體炸裂開來,場面讓人覺得觸目驚心,家庭成員有父親、母親、姐姐19歲、大弟17歲、二弟6歲、三妹4歲。 畫面一轉母親變身成為一隻白色貂,但是身形又像貂又像狐狸,不斷穿梭在大街小巷以及樹叢中繞路回家,路上一名帶著鴨舌帽的男人察覺到了!
談到少數族群,多數人會先聯想到原住民、新住民等族群 似乎大多數人是屬於多數的族群 事實上多數和少數是相對而言的 多數和少數也是可變的 更甚者,每個人都永遠是屬於少數的孤獨個體
Thumbnail
在思考「島嶼錨固」方案【註 1】時,我曾碰到一個問題:離岸多遠處才能捕捉到黑潮動能 ? 但,由於我沒有現場親眼看過黑潮,不敢保證上述說法是否屬實,希望日後能有機會驗證。 下列網址點進去也都能看到類似的圖片:
1、註冊地位於香港 由於中文貴金屬市場起步慢,在相關法律、監管機構欠缺的情況下,通常會有不良平臺鑽空子,以炒現貨貴金屬的旗號坑騙投資者。所以好的貴金屬交易平臺應該是怎樣的? 通常資質正規的平臺,其公司註冊地多坐落於香港繁榮地段,這是因為香港是老牌國際金融中心,貴金屬市場早已在當地紮根多年,擁有完善的
Thumbnail
關於標題〈可讀‧性〉,用字與模糊化的呈現方式,將在過去那些保守傳統的時代中,性與女性、同性等不可明言、不可言說、不被看見的狀態呈現出來了,那時候她/他們只能在渺茫曖昧的黑霧中摸索猜測,並且在逐漸明朗的現在讓文字清晰化,那些被汙名的過去,終於准許被寫出來,終於可讀(allow to read)。
Thumbnail
構造簡單,引擎從低速到高速作用圓滑。文氏管處之空氣速度幾乎保持一定,吸入空氣量隨真空及文氏管的開口面積而改變。高速時文氏管的開度大,進氣阻力小,容積效率高;低速時文氏管的空氣流速快,作用良好。