Scala 最佳實踐 純函式

2021-08-04 09:18:01 字數 3709 閱讀 4937

我們所處的是乙個命令式程式設計(imperative programming)的時代,這也是我們為何更喜歡用命令式風格寫**的原因。在我們周圍的一切都是可變的。雖然可變性並沒有那麼差勁,但是共享可變性就有點麻煩了。當我們引入共享可變性時,各種問題就會隨之而來。函式式風格是應對這類問題的乙個很好的方法。

函式式程式設計指的是僅通過使用純函式(pure function)和不可變值來完成軟體應用的編寫。

在本文,我們將會**純函式的一些內容。

純函式沒有任何***(中文維基:函式***),除了它的輸入以外,函式結果不依賴於其他任何事情。

對於給定的輸入,乙個純函式唯一的作用是就是產生乙個輸出 – 此外無任何作用。

可以將純函式想象為了乙個管道,有輸入流入,然後輸出流出,在流入流出的過程中沒有任何損耗。

下面是 scala 的乙個函式,它接收兩個值並返回它們的和:

scala> def add(a:int, b:int) = a + b

add: (a: int, b: int)int

這個函式沒有任何的***。它不會改變所提供的輸入值,而是利用了另乙個純函式,+操作符。作為該函式呼叫的結果,它返回了兩個值的和。這個add函式就是乙個純函式。

當我們使用純函式時,對於函式呼叫的先後順序並無顯式要求。

舉個例子,我們有兩個純函式:加法和乘法,它們接受兩個輸入值,乙個返回兩個值的和,乙個返回兩個值的積。因為這兩個函式是純函式,下面兩個不同順序的函式呼叫所產生的結果是相同的:

scala> def add(a:int,b:int) = a + b

add: (a: int, b: int)int

scala> def multiply(a:int,b:int) = a * b

multiply: (a: int, b: int)int

scala> add(5,8) + multiply(5,8)

res0: int = 53

scala> multiply(5,8) + add(5,8)

res1: int = 53

不過,如果我們的計算涉及對乙個非純函式的呼叫,就不能像上面這樣隨意調換順序進行呼叫了。出於優化角度,可以對使用純函式的表示式的呼叫順序進行重新安排,這樣所產生的結果與之前是完全相同的。

函式式程式設計的乙個主要原則就是寫出核心為純函式的應用,這樣一來,那麼***就會只存在於佔比不多的外層結構。

純函式的好處有:

易推斷這是因為乙個純函式,它沒有任何***,也沒有隱藏的 i/o 資訊,僅通過檢視它的簽名就能知道這個函式是幹什麼的。

易組合乙個純函式接受乙個輸入,然後對輸入進行一些計算,最後返回乙個結果。因為「輸出只依賴於輸入」,所以它不會改變周圍的任何事情,這便使得純函式易於組合起來形成簡單的解決方案。

易測試比起非純函式,純函式要容易測試的多。舉個例子:

scala> def purefunction(name : string) = s"my name is $name"

purefunction: (name: string)string

scala> def impurefunction(name : string) = println(s"my name is $name")

impurefunction: (name: string)unit

如果想要測試函式purefunction, 一行**就足夠了:

assert(purefunction("shivangi") == "my name is shivangi)"
而測試impurefunction就要複雜得多了,因為我們需要重定向標準輸出,然後在上面進行斷言。

易除錯因為乙個純函式的輸出僅依賴於函式的輸入和演算法本身,在除錯時,根本不用關心函式外部的資訊,所以純函式比非純函式更易於除錯。

易並行通過函式式程式設計很容易寫出並行/併發的應用。原因如下:

如果在兩個純表示式中沒有資料依賴,那麼它們的呼叫順序就可以進行調換,或者可以被並行執行而彼此不會相互影響(換句話說,任何純表示式的求值都是執行緒安全的))。

引用透明

引用透明(referentially transparent)指的是乙個表示式或函式可以被相應的數值進行安全替換。對於所有的引用透明值x,如果表示式f(x)是引用透明的,那麼這個函式就是純函式。

現在讓我們來看一下到底引用透明是什麼。

引用透明是乙個函式屬性,它指的是函式不受臨時的上下文影響,沒有任何***。對乙個特定的輸入而言,乙個引用透明的呼叫可以在不改變程式語義的情況下被它的結果所代替。

比如,輸入+ 3*2可以被替換為輸入+ 6,因為子表示式3*2是引用透明的。

我們為什麼要關心引用透明呢???

引用透明在程式優化中扮演了乙個非常重要的角色。如果能夠在編譯期用乙個函式或表示式的值來替換該函式或表示式,將會節省執行期的很多時間。

「引用透明」 指的是表示式的值僅依賴於其自身值,而不依賴於其他任何內容。

冪等冪等(idempotent)(中文維基:冪等)這個詞有多重含義,不過在這裡我們僅關注它在程式設計上的意義。給定乙個值,如果乙個函式或操作不論執行多次或僅執行一次,所得結果都是相同的,那麼我們就說這個函式或操作時冪等的。加法函式就是冪等的,它可以被執行任意多次。對於給定的 a 和 b,如果我們呼叫多少次,所得結果都是一樣的。

純函式就是冪等的。給定乙個輸入,基於該輸入值,我們呼叫乙個純函式一次,會產生乙個輸出值。給定同樣的輸入,基於該輸入值,我們呼叫乙個相同的純函式多次,所產生的輸出值是與呼叫一次完全相同的。

冪等的好處就是純函式可以被安全地執行任意多次,甚至如果我們不需要該函式結果的話,完全可以跳過不執行。

引用透明說的是乙個純函式可以被安全地替換為函式的輸出值。冪等說的是重複計算任意多次是完全沒問題的。這兩個特性組合起來就是說,處理純函式很容易,而且很完全 – 這給程式優化提供了極大的便利。

記憶記憶(memoizable)是乙個優化技術。它的目的在於以空間換時間,也就是說,通過儲存或快取計算結果來減少計算時間。

只有當給定引數或輸入,函式結果是完全相同的,記憶才變得有意義。顯然純函式具備這個屬性,因此它們很容易進行記憶。

延遲處理

延遲求值(lazy evaluation)指的是只有當需要乙個表示式的值時,才會該表示式進行求值。如果在程式執行過程中,這個值從來沒有被用到,那麼可能就根本不會對該表示式求值。在 scala 中,我們可以通過標記一些變數進行延遲處理。

延遲處理的好處就是,我們變得更有效率了,而這種效率的提公升並非通過更快地執行程式,而是通過消除我們不需要執行的操作。通過這種消除計算的方式,我們可以變得十分有效率。

純函式 是函式式程式設計中乙個根本的概念。對於乙個純函式,你可以立即求值,也可以放心大膽地放在後面求值。此外,因為無論我們求值多少次,何時求值,乙個純函式的結果總是唯一的,所以我們可以儲存求值的結果(通過延遲處理標記)並進行重用。還有,如果乙個函式沒有任何***,對於想要知道該函式是否已經被求值的任何人,方法就是檢視函式結果。函式計算也可以根據需要進行延遲計算。由於引用透明和記憶特性,對於程式優化也非常有幫助。

參考:文字譯自:scala best practices: pure functions

Scala編碼規範與最佳實踐 測試

測試 測試類應該與被測試類處於同一包下。如果使用spec2或scalatest的flatspec等,則測試類的命名應該為 被測類名 spec 若使用junit等框架,則測試類的命名為 被測試類名 test 測試含有具體實現的trait時,可以讓被測試類直接繼承trait。例如 trait recor...

最佳實踐 Flutter 最佳實踐

最佳實踐是乙個領域可以接受的專業標準,對於任何程式語言來說,提高 質量 可讀性 可維護性和健壯性都非常重要。讓我們探索一些設計和開發flutter應用程式的最佳實踐。class enum typedef和extension應採用駝峰命名uppercamelcase規則。class mainscree...

JUnit最佳實踐

junit最佳實踐 cherami 轉貼 參與分 20053,專家分 4960 發表 2003 9 16 下午7 57 版本 1.0 閱讀 3899次 martin fowler說過 當你試圖列印輸出一些資訊或除錯乙個表示式時,寫一些測試 來替代那些傳統的方法。一開始,你會發現你總是要建立一些新的f...