本节翻译自

  • Type Inference
  • Higher-order Functions
  • Nested Methods
  • Multiple Parameter Lists (Currying)

综述:Scala混合了面向对象和函数式的特性。在函数式编程语言中,函数是“头等公民”,可以像任何其他数据类型一样被传递和操作。在本节中,我们将会看到如何通过那些使用或返回函数的函数来提高我们的工作效率。

类型推断

Scala 编译器通常可以推断出表达式的类型,因此你不必显式声明它。

省略类型

val businessName = "Montreux Jazz Café"

编译器可以检测到 businessName 是一个 String。它的工作原理与方法类似:

def squareOf(x: Int) = x * x

编译器可以推断返回类型是一个 Int,所以不需要显式的返回类型。

对于递归方法,编译器不能推断出结果类型。由于这个原因,下面的程序会使编译器失败:

def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1)

当多态方法被调用或者泛型类被实例化时,也不用强制地指定类型参数。Scala 编译器会从上下文和实际方法/构造函数参数的类型中推断出这些缺失的类型参数。

这里有两个例子:

case class MyPair[A, B](x: A, y: B);
val p = MyPair(1, "scala") // type: MyPair[Int, String]def id[T](x: T) = x
val q = id(1)              // type: Int

编译器使用 MyPair 的参数类型来确定 AB 的类型。类似于 x 的类型。

参数

编译器从不推断方法参数类型。但是,在某些情况下,它可以在函数作为参数传递时推断匿名函数参数类型。

Seq(1, 3, 4).map(x => x * 2)  // List(2, 6, 8)

map 的参数是 f: A => B。因为我们把整数放在 Seq 中,编译器知道 AInt(即 x 是一个整数)。因此,编译器可以从 x * 2 推断出 BInt 类型。

何时不依靠类型推断

通常认为在公共 API 中声明成员的类型更具可读性。因此,我们建议你对任何将暴露给用户的代码 API 进行明确的类型标记。

此外,类型推断有时可能推断出一个太特定的类型。 假设我们写:

var obj = null

我们现在不能继续进行重新分配:

obj = new AnyRef

它不会编译,因为 obj 推断的类型是 Null。由于该类型的唯一值为空,因此不可能分配不同的值。

高阶函数

Scala 允许对高阶函数的定义。这些函数将其他函数作为参数,或者其结果是一个函数。这是可能的,因为函数在 Scala 中是一等公民。在这一点上,术语可能会有些混乱,我们使用短语“高阶函数”来表示++将函数作为参数++或++返回函数++的方法和函数。

最常见的例子之一是可用于Scala中的集合的高阶函数 map

val salaries = Seq(20000, 70000, 40000)
val doubleSalary = (x: Int) => x * 2
val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000)

doubleSalary 是一个输入一个 Int x 返回 x * 2 的函数。通常,箭头左侧的元组 => 是一个参数列表,右侧表达式的值是返回值。在第三行,函数 doubleSalary 被应用于工资列表中的每个元素。

为了简化代码,我们可以使函数匿名并直接将其作为参数传递给 map:

val salaries = Seq(20000, 70000, 40000)
val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000)

注意 x 在上面的例子中没有被声明为 Int。这是因为编译器可以根据函数映射的类型推断出类型。下面是以一种更习惯的方法写的同一段代码:

val salaries = Seq(20000, 70000, 40000)
val newSalaries = salaries.map(_ * 2)

由于Scala编译器已经知道参数的类型(一个 Int),因此你只需提供该函数的右侧。唯一需要注意的是,你需要使用_代替参数名称(在前面的例子中是x)。

强迫方法转化为函数

也可以将方法作为参数传递给高阶函数,因为 Scala 编译器会将该方法强制为一个函数。

case class WeeklyWeatherForecast(temperatures: Seq[Double]) {private def convertCtoF(temp: Double) = temp * 1.8 + 32def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF
}

这里 convertCtoF 方法被传递给 forecastInFahrenheit。这是可能的,因为编译器将 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 {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 bigPromotion(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

注意 urlBuilder (String, String) => String 的返回类型。这意味着返回的匿名函数需要两个字符串并返回一个字符串。在这个例子里,返回的匿名函数是 (endpoint: String, query: String) => s"https://www.example.com/$endpoint?$query"

嵌套方法

在Scala中,方法可以嵌套方法。下面的对象提供了计算一个给定数字的阶乘的 factorial 方法:

def factorial(x: Int): Int = {def fact(x: Int, accumulator: Int): Int = {if (x <= 1) accumulatorelse fact(x - 1, x * accumulator)}  fact(x, 1)
}println("Factorial of 2: " + factorial(2))
println("Factorial of 3: " + factorial(3))

这个程序的输出是:

Factorial of 2: 2
Factorial of 3: 6

柯里化

方法可以定义多个参数列表。当使用较少数量的参数列表调用某个方法时,这将产生一个将缺少的参数列表作为其参数的函数 这种形式上被称为柯里化。

下面是一个例子,定义在 Scala 集合的 Traversable 特质中:

def foldLeft[B](z: B)(op: (B, A) => B): B

foldLeft 将二元运算符 op 应用于初始值 z 以及此可遍历的所有元素,从左到右。下面显示的是它的用法示例。

从初始值 0 开始,foldLeft 在这里将函数 (m, n) => m + n 应用于 List 中的每个元素和先前的累加值。

val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val res = numbers.foldLeft(0)((m, n) => m + n)
print(res) // 55

多个参数列表具有更详细的调用语法;因此应该谨慎使用。建议的使用案例包括:

单函数参数

在单个函数参数的情况下,如上面 foldLeftop 所示,多个参数列表允许使用简洁的语法将匿名函数传递给方法。没有多个参数列表,代码将如下所示:

numbers.foldLeft(0, {(m: Int, n: Int) => m + n})

请注意,此处使用多个参数列表使我们能够利用 Scala 类型推断来使代码更加简洁,如下所示;这在一个没有柯里化定义的函数中是不可能的。

numbers.foldLeft(0)(_ + _)

另外,它允许我们修改参数 z 并传递一个部分函数并重用它,如下所示:

val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val numberFunc = numbers.foldLeft(List[Int]())_val squares = numberFunc((xs, x) => xs:+ x*x)
print(squares.toString()) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)val cubes = numberFunc((xs, x) => xs:+ x*x*x)
print(cubes.toString())  // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000)

隐式参数

要将参数列表中的某些参数指定为 implicit,应使用多个参数列表。一个例子是:

def execute(arg: Int)(implicit ec: ExecutionContext) = ???

换名参数

换名参数 只在使用时进行求值。它们与换值参数 形成对比。要创建一个换名参数,只需将 => 添加到其类型前。

def calculate(input: => Int) = input * 37

换名参数具有一个优点:如果不在函数体中使用则不求值的优点。另一方面,换值参数的优点是它们只被求值一次。

下面是我们如何实现 while 循环的一个示例:

def whileLoop(condition: => Boolean)(body: => Unit): Unit =if (condition) {bodywhileLoop(condition)(body)}var i = 2whileLoop (i > 0) {println(i)i -= 1
}  // prints 2 1

方法 whileLoop 使用多个参数列表来获取一个条件和一个循环体。如果 conditiontrue,则执行 body,然后执行了一个递归调用。如果 conditionfalse,则 body 从未被求值过,因为我们在 => 前面加了 body 类型。

现在,当我们通过 i > 0 作为我们的 conditionprintln(i)i -= 1 作为 body,它的表现就像许多语言中的标准 while 循环。

如果这个参数是计算密集型的,或者需要一个长时间运行的代码块,比如获取URL,那么在使用参数之前,延迟对参数进行求值的能力可以帮助提高性能。

【Scala之旅】高阶函数相关推荐

  1. scala中的高阶函数_Scala中的高阶函数(HOF)

    scala中的高阶函数 Higher Order Functions (HOF) in Scala are the very core of this functional programming l ...

  2. 2021年大数据常用语言Scala(三十七):scala高级用法 高阶函数用法

    目录 高阶函数用法 作为值的函数 匿名函数 柯里化(多参数列表) 闭包 高阶函数用法 Scala 混合了面向对象和函数式的特性,在函数式编程语言中,函数是"头等公民",它和Int. ...

  3. Scala中的高阶函数

    Scala混合了面向对象和函数式的特性.在函数式编程语言中,函数是"头等公民",可以像任何其他数据类型一样被传递和操作.每当你想要给算法传入明细动作时这个特性就会变得非常有用.在函 ...

  4. scala入门之高阶函数案例

    基于Java学习scala 高阶函数练习案例 1.定义一个高阶函数,按照指定的规则对集合里面的每个元素进行操作 2.定义一个高阶函数,按照指定的规则对集合中的所有元素进行聚合 3.定义一个高阶函数,按 ...

  5. Scala高阶函数详解

    概述 高阶函数主要有两种:一种是将一个函数当做另外一个函数的参数(即函数参数):另外一种是返回值是函数的函数. 用函数作为形参或返回值的函数,称为高阶函数. (1)使用函数作为参数 //函数参数,即传 ...

  6. Scala入门到精通——第十三节 高阶函数

    本节主要内容 高阶函数简介 Scala中的常用高阶函数 SAM转换 函数柯里化 部分应用函数 1. 高阶函数简介 高阶函数主要有两种:一种是将一个函数当做另外一个函数的参数(即函数参数):另外一种是返 ...

  7. Scala - 快速学习08 - 函数式编程:高阶函数

    函数式编程的崛起 函数式编程中的"值不可变性"避免了对公共的可变状态进行同步访问控制的复杂问题,能够较好满足分布式并行编程的需求,适应大数据时代的到来. 函数是第一等公民 可以作为 ...

  8. Scala入门到精通——第五节 函数、高阶函数与闭包

    本节主要内容 (一)函数字面量(值函数) (二)匿名函数 (三)函数的简化 (四)函数参数 (四)闭包 函数字面量(值函数) 函数字面量(function literal),也称值函数(functio ...

  9. Scala学习(十二)高阶函数

    2019独角兽企业重金招聘Python工程师标准>>> 1.作为值的函数 在Scala中,你可以在变量中存放函数: import scala.math._val num = 3.14 ...

最新文章

  1. 宏基因组理论教程2扩增子分析
  2. angularjs 中的scope继承关系——(2)
  3. 二叉树的先序、中序、后续遍历【Java】
  4. 创建设计模式 - 工厂设计模式
  5. 统计字符串中个字符的个数
  6. ubuntu之安装sublime text
  7. 静态密码已经OUT 探索身份验证新方式
  8. 如何避免_如何避免钢板弹簧受损
  9. IGBT失效模式和失效现象
  10. ppt太大怎么压缩整个文件
  11. 入侵手游服务器修改数据库,如何入侵手游服务器数据库
  12. 论文 毕业设计 相关 用语 评语
  13. Parsing error: No Babel config file detected for ....
  14. 中山医06年考研初试复试全攻略!( 完整版)
  15. CSS雪碧图制作emoji表情包
  16. The Shawshank Redemption-16
  17. 前端技术面试核心问题(持续更新)
  18. time_t c语言 2038,什么是2038问题?
  19. GRAFANA接入第三方SSO
  20. 俞敏洪在北大开学典礼上的演讲[转帖]

热门文章

  1. Freemarker判断对象是否为空的用法
  2. 如何将excel里的数据批量导入ACCESS,要用vb代码?
  3. nginx与IIS服务器搭建集群实现负载均衡(三)
  4. 英语总结系列(八):回顾八月展望九月
  5. 逼真照片随手画,马良神笔已上线 | 点击收获这份英伟达GauGAN开源代码
  6. 喜欢赌球的你,可能被AI盯上了
  7. 机器翻译简史:八十多年来,人类就是要再造一座通天塔
  8. 腾讯围棋AI绝艺战胜DeepZenGo又夺一冠
  9. 苹果智能音箱HomePod跳票了,上市日期推迟到明年
  10. Java最大值和最小值