Scala 提供的另一個有助於撰寫函數式程式碼的功能,是撰寫純函數的能力。純函數可以這樣定義
- 如果函數
f
是純函數,則在給定相同的輸入x
時,它總是會傳回相同的輸出f(x)
- 函數的輸出僅取決於其輸入變數及其實作
- 它只會計算輸出,而不會修改其周遭的世界
這表示
- 它不會修改其輸入參數
- 它不會變異任何隱藏狀態
- 它沒有任何「後門」:它不會從外部世界(包括控制台、網路服務、資料庫、檔案等)讀取資料,或寫入資料到外部世界
根據這個定義,任何時候你使用相同的輸入值呼叫純函數,你都將會得到相同的結果。例如,你可以使用輸入值 2
無限次呼叫 double
函數,而你將會永遠得到結果 4
。
純函數範例
根據這個定義,你可以想像,scala.math._ 套件中的這些方法是純函數
abs
ceil
max
這些 String
方法也是純函數
isEmpty
length
substring
Scala 集合類別中的大多數方法也作為純函數運作,包括 drop
、filter
、map
,以及更多。
在 Scala 中,函數 和 方法 幾乎可以完全互換,因此即使我們使用常見的產業術語「純函數」,這個術語可以用來描述函數和方法。如果你有興趣了解方法如何像函數一樣使用,請參閱 Eta 展開 討論。
非純函數範例
相反地,下列函數是非純函數,因為它們違反了定義。
println
– 與主控台、檔案、資料庫、網路服務、感測器等互動的方法都是不純的。currentTimeMillis
– 與日期和時間相關的方法都是不純的,因為其輸出取決於其輸入參數以外的其他因素sys.error
– 拋出例外的方法是不純的,因為它們不只回傳結果
不純函式通常會執行下列一項或多項操作
- 從隱藏狀態讀取,亦即存取未明確傳遞至函式中作為輸入參數的變數和資料
- 寫入隱藏狀態
- 變更其所給定的參數,或變更隱藏變數,例如其所包含類別中的欄位
- 執行某種形式的 I/O 與外界互動
一般來說,您應注意回傳類型為
Unit
的函式。由於這些函式不回傳任何內容,因此邏輯上您呼叫它們的唯一原因是為了達成某些副作用。因此,這些函式的使用通常是不純的。
但需要不純函式…
當然,如果應用程式無法讀取或寫入外界,就不會很有用,因此人們提出此建議
使用純函式撰寫應用程式的核心,然後在該核心周圍撰寫不純的「包裝器」以與外界互動。正如某人曾經說過的,這就像在純蛋糕上放一層不純的糖霜。
請務必注意,有方法可以讓不純的與外界互動感覺更純。例如,您會聽說使用 IO
Monad 來處理輸入和輸出。這些主題超出了本文檔的範圍,因此為了簡化起見,可以認為 FP 應用程式有一個純函式核心,並用其他函式包裝起來與外界互動。
撰寫純函數
注意:本節中,常見的產業術語「純函數」通常用於指稱 Scala 方法。
若要撰寫 Scala 中的純函數,請使用 Scala 的方法語法撰寫(不過您也可以使用 Scala 的函數語法)。例如,以下是將輸入值加倍的純函數
def double(i: Int): Int = i * 2
如果您熟悉遞迴,以下是計算整數清單總和的純函數
def sum(xs: List[Int]): Int = xs match {
case Nil => 0
case head :: tail => head + sum(tail)
}
def sum(xs: List[Int]): Int = xs match
case Nil => 0
case head :: tail => head + sum(tail)
如果您了解該程式碼,您會發現它符合純函數定義。
重點
本節的第一個重點是純函數的定義
純函數是僅依賴其宣告的輸入和實作來產生輸出的函數。它只計算其輸出,不依賴或修改外部世界。
第二個重點是,每個真實世界的應用程式都會與外部世界互動。因此,思考函數式程式的一個簡化方式是,它們包含一個純函數核心,並以與外部世界互動的其他函數進行包裝。
本頁的貢獻者
內容
- 簡介
- Scala 特色
- 為何選擇 Scala 3?
- Scala 體驗
- Hello, World!
- REPL
- 變數和資料類型
- 控制結構
- 網域建模
- 方法
- 一級函數
- 單例物件
- 集合
- 脈絡抽象
- 頂層定義
- 摘要
- 初探類型
- 字串內插
- 控制結構
- 網域建模
- 工具
- OOP 建模
- FP 建模
- 方法
- 方法特徵
- Scala 3 中的主要方法
- 摘要
- 函式
- 匿名函式
- 函式變數
- Eta 擴充
- 高階函式
- 撰寫自己的 map 方法
- 建立會傳回函式的函式
- 摘要
- 封裝和匯入
- Scala 集合
- 集合類型
- 集合方法
- 摘要
- 函式程式設計
- 什麼是函式程式設計?
- 不可變值
- 純函數
- 函式是值
- 函式錯誤處理
- 摘要
- 類型和類型系統
- 推論類型
- 泛型
- 交集類型
- 聯集類型
- 代數資料類型
- 變異
- 不透明類型
- 結構化類型
- 依賴函式類型
- 其他類型
- 脈絡抽象
- 擴充方法
- 內容參數
- 內容範圍
- 已給定的匯入
- 類型類別
- 多重等式
- 隱式轉換
- 摘要
- 並行處理
- Scala 工具
- 使用 sbt 建構和測試 Scala 專案
- 工作表
- 與 Java 互動
- Java 開發人員的 Scala
- JavaScript 開發人員的 Scala
- Python 開發人員的 Scala
- 下一步該怎麼做