高阶函数将其他函数作为参数或将函数作为结果返回。这是可能的,因为函数在 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"
。