高階函式將其他函式作為參數,或傳回函式作為結果。這是可能的,因為函式在 Scala 中是一等值。術語在這個時候可能會有點混淆,我們使用短語「高階函式」來表示將函式作為參數或傳回函式的函式和方法。
在純粹的物件導向世界中,一個良好的做法是避免公開參數化函式的函式,這些函式可能會洩漏物件的內部狀態。洩漏內部狀態可能會破壞物件本身的不變式,從而違反封裝。
最常見的範例之一是高階函式 map
,它可用於 Scala 中的集合。
val salaries = Seq(20_000, 70_000, 40_000)
val doubleSalary = (x: Int) => x * 2
val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000)
doubleSalary
是一個函數,它接受一個 Int,x
,並傳回 x * 2
。一般來說,箭頭 =>
左邊的元組是一個參數清單,而右邊表達式的值就是傳回的內容。在第 3 行,函數 doubleSalary
套用在薪資清單中的每個元素。
為了縮小程式碼,我們可以讓函數匿名並直接傳遞給 map 作為參數
val salaries = Seq(20_000, 70_000, 40_000)
val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000)
請注意,在上述範例中,x
沒有宣告為 Int。這是因為編譯器可以根據 map 預期的函數類型推斷出類型(請參閱 柯里化)。撰寫相同程式碼段更慣用的方式如下
val salaries = Seq(20_000, 70_000, 40_000)
val newSalaries = salaries.map(_ * 2)
由於 Scala 編譯器已經知道參數的類型(一個 Int),你只需要提供函數的右側。唯一的注意事項是,你需要使用 _
取代參數名稱(在先前的範例中是 x
)。
將方法強制轉換為函數
也可以將方法傳遞給高階函數作為參數,因為 Scala 編譯器會將方法強制轉換為函數。
case class WeeklyWeatherForecast(temperatures: Seq[Double]) {
private def convertCtoF(temp: Double) = temp * 1.8 + 32
def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF
}
case class WeeklyWeatherForecast(temperatures: Seq[Double]):
private def convertCtoF(temp: Double) = temp * 1.8 + 32
def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF
在此,方法 convertCtoF
傳遞給高階函數 map
。這是可能的,因為編譯器會將 convertCtoF
強制轉換為函數 x => convertCtoF(x)
(注意:x
會是一個產生的名稱,保證在其範圍內是唯一的)。
接受函數的函數
使用高階函數的一個原因是減少重複的程式碼。假設你想要一些方法,可以將某人的薪資提高不同的倍數。如果不建立高階函數,它可能看起來像這樣
object SalaryRaiser {
def smallPromotion(salaries: List[Double]): List[Double] =
salaries.map(salary => salary * 1.1)
def greatPromotion(salaries: List[Double]): List[Double] =
salaries.map(salary => salary * math.log(salary))
def hugePromotion(salaries: List[Double]): List[Double] =
salaries.map(salary => salary * salary)
}
object SalaryRaiser:
def smallPromotion(salaries: List[Double]): List[Double] =
salaries.map(salary => salary * 1.1)
def greatPromotion(salaries: List[Double]): List[Double] =
salaries.map(salary => salary * math.log(salary))
def hugePromotion(salaries: List[Double]): List[Double] =
salaries.map(salary => salary * salary)
請注意,這三個方法只在乘法因子方面有所不同。為了簡化,你可以將重複的程式碼萃取到一個高階函數中,如下所示
object SalaryRaiser {
private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] =
salaries.map(promotionFunction)
def smallPromotion(salaries: List[Double]): List[Double] =
promotion(salaries, salary => salary * 1.1)
def greatPromotion(salaries: List[Double]): List[Double] =
promotion(salaries, salary => salary * math.log(salary))
def hugePromotion(salaries: List[Double]): List[Double] =
promotion(salaries, salary => salary * salary)
}
object SalaryRaiser:
private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] =
salaries.map(promotionFunction)
def smallPromotion(salaries: List[Double]): List[Double] =
promotion(salaries, salary => salary * 1.1)
def greatPromotion(salaries: List[Double]): List[Double] =
promotion(salaries, salary => salary * math.log(salary))
def hugePromotion(salaries: List[Double]): List[Double] =
promotion(salaries, salary => salary * salary)
新方法 promotion
,採用薪資加上一個型別為 Double => Double
的函式(也就是一個接收 Double 並回傳 Double 的函式),並回傳乘積。
方法和函式通常會表達行為或資料轉換,因此擁有可根據其他函式組合的函式,有助於建構泛用機制。這些泛用操作會延後鎖定整個操作行為,讓客戶端能夠控制或進一步自訂操作本身的部份。
回傳函式的函式
在某些情況下,您會想要產生一個函式。以下是回傳函式的其中一個方法範例。
def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = {
val schema = if (ssl) "https://" else "http://"
(endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query"
}
val domainName = "www.example.com"
def getURL = urlBuilder(ssl=true, domainName)
val endpoint = "users"
val query = "id=1"
val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String
def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String =
val schema = if ssl then "https://" else "http://"
(endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query"
val domainName = "www.example.com"
def getURL = urlBuilder(ssl=true, domainName)
val endpoint = "users"
val query = "id=1"
val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String
請注意 urlBuilder 的回傳型別 (String, String) => String
。這表示回傳的匿名函式會接收兩個字串,並回傳一個字串。在此情況下,回傳的匿名函式為 (endpoint: String, query: String) => s"https://www.example.com/$endpoint?$query"
。