天天看點

讀函數式程式設計思維筆記03_權責讓渡

作者:躺着的柒
讀函數式程式設計思維筆記03_權責讓渡

讀函數式程式設計思維筆記03_權責讓渡

1.觀點

1.1.抽象隐藏了繁雜的細節,隻是有時候會連同重要的考慮因素一起隐藏掉

1.2.了解掌握的抽象層次永遠要比日常使用的抽象層次更深一層

1.3.交出控制權的觀點:放棄對繁瑣細節的掌控,關注問題域,而非關注問題域的實作

2.函數式思維的好處

2.1.将低層次細節(如垃圾收集)的控制權移交給運作時,進而消弭了一大批注定會發生的程式錯誤

2.2.函數式語言的簡潔文法和靈活配合,才使遞歸成為簡單可行的代碼重用選項之一

2.3.運作時有能力在涉及效率的問題上替我們做決定

2.4.從頻繁出現的場景中消滅掉煩人的實作細節

3.閉包(closure)

3.1.一種特殊的函數,在生成的時候,會把引用的變量全部圈到代碼塊的作用域裡,封閉、包圍起來(故名閉包)

3.1.1.閉包作為一種對行為的模組化手段,讓我們把代碼和上下文同時封裝在單一結構,也就是閉包本身裡面,像傳統資料結構一樣可以傳遞到其他位置,然後在恰當的時間和地點完成執行

3.2.閉包的每個執行個體都保有自己的一份變量取值,包括私有變量也是如此

3.2.1.代碼塊執行個體從它被建立的一刻起,就持有其作用域内一切事物的封閉副本

3.3.在缺乏閉包特性的舊版Java平台上,Functional Java利用匿名内部類來模仿“真正的”閉包的某些行為,但語言的先天不足導緻這種模仿是不徹底的

3.4.當作一種異地執行的機制,用來傳遞待執行的變換代碼

3.5.是推遲執行原則的絕佳樣闆

3.6.抓住上下文,而非狀态

3.6.1.“讓運作時去管理狀态”

4.柯裡化(currying)和函數的部分施用(partial application)

4.1.向一部分參數代入一個或多個預設值的辦法來實作的

4.1.1.這部分參數被稱為“固定參數”

4.2.柯裡化

4.2.1.從一個多參數函數變成一連串單參數函數的變換

4.2.2.結果是傳回鍊條中的下一個函數

4.3.部分施用

4.3.1.通過提前代入一部分參數值,使一個多參數函數得以省略部分參數,進而轉化為一個參數數目較少的函數

4.3.2.把參數的取值綁定到使用者在操作中提供的具體值上,因而産生一個“元數”(參數的數目)較少的函數

4.4.Groovy

4.4.1.curry()函數實作柯裡化

4.5.Clojure

4.5.1.(partial f a1 a2 …)函數

4.5.2.沒有将柯裡化實作成一種語言特性,相關的場景交由部分施用去處理

4.6.Scala

4.6.1.柯裡化

4.6.2.部分施用函數

4.6.3.偏函數

4.6.3.1.PartialFunction trait是為了密切配合語言中的模式比對特性

4.6.3.2.trait并不生成部分施用函數。它的真正用途是描述隻對定義域中一部分取值或類型有意義的函數

4.6.3.3.Case語句是偏函數的一種用法

4.6.3.4.偏函數的參數被限定了取值範圍

4.6.3.5.可以把偏函數用在任何類型上,包括Any

4.7.大多數函數式語言都具備柯裡化和部分施用這兩種特性,但實作上各有各的做法

4.8.用途

4.8.1.函數工廠

4.8.1.1.工廠方法的場合,正适合柯裡化(以及部分施用)表現它的才幹

4.8.2.Template Method(模闆方法)模式

4.8.2.1.在固定的算法架構内部安排一些抽象方法,為後續的具體實作保留一部分靈活性

4.8.3.隐含參數

5.遞歸

5.1.以一種自相似的方式來重複事物的過程

5.2.對一個不斷變短的清單反複地做同一件事,把遞歸用在這樣的場合,寫出來的代碼就容易了解

5.3.遞歸操作往往受制平台而存在一些固有的技術限制,是以這種技法絕非萬靈藥

5.4.但對于長度不大的清單來說,遞歸操作是安全的

5.5.語言在管理傳回值,它從遞歸棧裡收集每次方法調用的傳回結果,構造出最終的傳回值

5.6.利用遞歸,把狀态的管理責任推給運作時

6.記憶(memoization)

6.1.用更多的記憶體(我們一般不缺記憶體)去換取長期來說更高的效率

6.1.1.緩存可以提高性能,但緩存有代價:它提高了代碼的非本質複雜性和維護負擔

6.1.2.負責編寫緩存代碼的開發者不僅要顧及代碼的正确性,連它的執行環境也要考慮在内

6.1.3.代碼中的狀态,開發者不僅要費心照應它,還要條分縷析它的一切明暗牽連

6.2.記憶的内容應該是值不可變的

6.3.保證所有被記憶的函數

6.3.1.沒有副作用

6.3.2.不依賴任何外部資訊

6.4.隻有純(pure)函數才可以适用緩存技術

6.4.1.純函數是沒有副作用的函數

6.4.1.1.它不引用其他值可變的類字段

6.4.1.2.除傳回值之外不設定其他的變量

6.4.1.3.其結果完全由輸入參數決定

6.4.2.隻有在函數對同樣一組參數總是傳回相同結果的前提下,我們才可以放心地使用緩存起來的結果

6.5.緩存是很常見的一種需求,同時也是制造隐晦錯誤的源頭

6.6.兩種情況

6.6.1.類内部緩存

6.6.1.1.類中的緩存就代表類有了狀态,所有與緩存打交道的方法都不可以是靜态的,于是産生了更多的連鎖效應

6.6.2.外部調用

6.7.兩種實作方式

6.7.1.手工進行狀态管理

6.7.2.采用記憶機制

6.8.在指令式的思路下,開發者是代碼的主人(以及一切責任的承擔者)

6.9.我們寫出來的緩存絕不可能比語言設計者産生的更高效,因為語言設計者可以無視他們給語言定的規矩:開發者無法觸碰的底層設施,不過是語言設計者手中的玩物,他們擁有的優化手段和空間是“凡人”無法企及的

6.9.1.上帝視角

6.10.Groovy

6.10.1.先将要記憶的函數定義成閉包,然後對該閉包執行memoize()方法來獲得一個新函數,以後我們調用這個新函數的時候,其結果就會被緩存起來

6.10.2.memoizeAtMost(1000)

6.11.Clojure

6.11.1.(memoize )

6.12.Scala

6.12.1.沒有直接提供記憶機制,但它為集合提供的getOrElseUpdate()方法已經替我們承擔了大部分的實作工作

6.13.Java 8

6.13.1.沒有直接提供記憶特性,但隻要借助它新增的lambda特性,就可以輕松地實作記憶功能

7.緩求值(lazy evaluation)

7.1.盡可能地推遲求解表達式

7.1.1.昂貴的運算隻有到了絕對必要的時候才執行

7.1.2.可以建立無限大的集合,隻要一直接到請求,就一直送出元素

7.1.3.按緩求值的方式來使用映射、篩選等函數式概念,可以産生更高效的代碼

7.1.4.減少占用的存儲空間。假如能夠用推導的方法得到後續的值,那就不必預先存儲完整的清單了——這是犧牲速度來換取存儲空間的做法

7.2.非嚴格求值(non-strict)的(也叫緩求值,lazy)

7.2.1.常用的非嚴格求值語言有Haskell

7.3.Totally Lazy架構(Java)

7.4.Groovy

7.4.1.緩求值清單是函數式語言普遍具備的特性

7.4.1.1.LazyList

7.4.2.暫緩初始化昂貴的資源,除非到了絕對必要的時候

7.4.3.可以用來建構無限序列,也就是沒有上邊界的清單

7.4.4.緩求值清單特别适用于資源的生産成本較高的情況

7.5.Clojure

7.5.1.資料結構都是預設緩求值的

7.6.Scala

7.6.1.沒有把一切都預設安排成緩求值的,而是在集合之上另外提供了一層緩求值的視圖

7.7.緩求值的字段初始化

7.7.1.Scala

7.7.1.1.val聲明前面加上“lazy”字樣

7.7.1.1.1.令字段從嚴格求值變成按需要求值

7.7.2.Groovy

7.7.2.1.抽象文法樹(Abstract Syntax Tree,AST)

7.7.2.1.1.@Lazy标注

8.元函數技法

8.1.操縱的對象是函數本身,而非函數的結果

8.2.柯裡化

繼續閱讀