快学Scala第12章----高阶函数

本章要点

  • 在Scala中函数是“头等公民”,就和数字一样;
  • 你可以创建匿名函数,通常还会把它们交给其他函数;
  • 函数参数可以给出需要稍后执行的行为;
  • 许多集合方法都接受函数参数,将函数应用到集合中的值;
  • 有很多语法上的简写让你以简短且易读的方式表达函数参数;
  • 你可以创建操作代码块的函数,它们看上去就像是内建的控制语句。

作为值的函数

在Scala中,你可以在变量中存放函数:

import scala.math._
val num = 3.14
val fun = ceil _

这段代码将num设为3.14, fun设为ceil函数。
说明: ceil函数后的 _ 意味着你确实指的是这个函数,而不是碰巧忘记给它传递参数。 从技术上讲, _ 将ceil方法转成了函数,在Scala中,你无法直接操纵方法,而只能直接操纵函数。

fun.png

怎么使用函数:

  • 调用它
  • 传递它,存放在变量中,或者作为参数传递给另一个函数
fun(num)  // fun是一个包含函数的变量,而不是一个固定的函数
Array(3.14, 1.42, 2.0).map(fun)  // 将fun传递给另一个函数, Array(4.0, 2.0, 2.0)

匿名函数

在Scala中,你不需要给每个函数命名,它就是匿名函数:

(x: Double) => 3 * x
// 将这个函数存放在变量中
val triple = (x: Double) => 3 * x
// 这和用def一样
def triple(x: Double) = 3 * x

// 作为参数传递
Array(3.14, 1.42, 2.0).map((x: Double) => 3 * x)
Array(3.14, 1.42. 2.0).map{ (x: Double) => 3 * x }   // 也可以使用花括号
Array(3.14, 1.42. 2.0) map { (x: Double) => 3 * x }   // 使用中置表示法

带函数参数的函数

def valueAtOneQuarter(f: (Double) => Double) = f(0.25)
valueAtOneQuarter(ceil _)    // 1.0
valueAtOneQuarter(sqrt _)   // 0.5

这里的参数是一个接受Double并返回Double的函数。而valueAtOneQuarter的函数类型是:((Double) => Double) => Double ; 一个接受函数参数的函数,它就称作高阶函数。例如valueAtOneQuarter。
高阶函数也可以产出另一个函数,即返回一个函数:

def mulBy(factor: Double) = (x: Double) => factor * x
mulBy(3) // 返回函数 (x: Double) => 3 * x

mulBy函数的威力在于,它可以产出能够乘以任何数额的函数:

val quintuple = mulBy(5)
quintuple(20)    // 100

mulBy函数的类型为: (Double) => ( (Double) => Double)


参数(类型)推断

Scala有比较强大的参数推导:

def valueAtOneQuarter(f: (Double) => Double) = f(0.25)
valueAtOneQuarter( (x: Double) => 3 * x )  // 0.75
// 可以简单写成
valueAtOneQuarter( (x) => 3 * x )  // 0.75
// 只有一个参数的情况,还可以省却参数的括号:
valueAtOneQuarter( x => 3 * x )  // 0.75
// 如果参数在 => 右侧只出现一次,可以用 _ 替换它
valueAtOneQuarter( 3 * _ )  // 0.75

这些简写方式仅在参数类型已知的情况下有效:

val fun = 3 * _  // error
val fun = 3 * (_: Double)  // OK
val fun: (Double) => Double = 3 * _  // OK

一些有用的高阶函数

(1 to 9).map(0.1 * _)    // 应用于所有元素
(1 to 9).map("*" * _).foreach(println _)

(1 to 9).filter(_ % 2 == 0)  // 2,4,6,8

// reduceLeft方法接受一个二元的函数,将它应用到序列中的所有元素,从左到右
(1 to 9).reduceLeft(_ * _)   // 1*2*3*...*8*9

// 排序
"Mary has a little lamb".split(" ").sortWith(_.length < _.length)

闭包

函数可以在变量不再处于作用于内时被调用。这样的函数称为闭包, 闭包由代码和代码用到的任何非局部变量定义构成。 例如:

def mulBy(factor: Double) = (x: Double) => factor * x
// 如下调用
val triple = mulBy(3)
val half = mulBy(0.5)
println(triple(14) + " " + half(14))   // 42 7

mulBy首次调用时将参数变量factor设为3, 该变量在(x: Double) => factor * x 函数的函数体内被引用,该函数被存入triple。然后参数变量factor从运行时的栈上被弹出;
mulBy第二次被调用时,参数变量被设为了0.5, 该变量在(x: Double) => factor * x 函数的函数体内被引用,该函数被存入half 。
这样,每一个返回的函数都要自己的factor设置。在这里triple和half存储的函数访问了它们作用于范围外的变量。


SAM转换

在Scala中,你可以传递函数作为参数,而在Java中是不可以的(目前),其通常的做法是将动作放在一个实现某接口的类中,然后将该类的一个实例传递给另一个方法。在很多时候,这些接口都只有单个抽象方法(single abstract method), 简称SAM类型。 例如:

var counter = 0

val button = new JButton("Increment")
button.addActionListener(new ActionListener {
  override def actionPerformed(event: ActionEvent) {
    counter += 1
  }
})

这里使用了样板代码,我们希望的是只传递一个函数给addActionListener就好了:

button.addActionListener((event: ActionEvent) => counter += 1)

为了启用这个语法,你需要提供一个隐士转换,因为addActionListener是Java的方法。

implicit def makeAction(action: ( (ActionEvent) => Unit ) ) = {
  new ActionListener {
    override def actionPerformed(event: ActionEvent) { action(event) }
  }
}

只需要简单的把这个函数和你的界面代码放在一起就可以在需要传入ActionListener 对象的地方传入任何(ActionEvent) => Unit 类型的函数了。


柯里化

柯里化指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个原有的函数的第二个参数作为参数的函数。

def mul(x: Int, y: Int) = x * y
// 定一个接受一个参数,生成另一个接受一个参数的函数
def mulOneAtAtime(x: Int) = (y: Int) => x * y
mulOneAtAtime(6)(7) 

这里mulOneAtAtime(6)会返回(y: Int) => 6 * y 类型的函数,然后该函数又被调用,最终计算出结果
Scala支持如下简写来定义这样的柯里化函数:

def mulOneAtAtime (x: Int) (y: Int) = x * y

一个典型应用:corresponds方法可以比较两个序列是否在某个条件下相同:

val a = Array("Hello", "World")
val b = Array("hello", "world")
a.corresponds (b) (_.equalsIgnoreCase(_))

这里corresponds 是一个柯里化的函数,定义:

def corresponds[B] (that: Seq[B]) (p: (A, B) => Boolean): Boolean

that序列和p函数是分开的两个柯里化的参数,类型推断器先推断出that是一个String类型的序列,也就是B是String,然后才能在p函数中推断出函数类型是(String, String) => Boolean。


控制抽象

对于一个没有参数也没有返回值的函数:

def runInThread(block: () => Unit) {
  new Thread {
    override def run() { block() }
  }.start()
}

runInThread { () => println("Hi"); Thread.sleep(1000); println("Bye") }

() => 这样看上比不那么美观,要向省掉() => 可以使用换名调用表示方法:在参数声明和调用该函数参数的地方略去(),但保留 => :

def runInThread(block: => Unit) {
  new Thread {
    override def run() { block }
  }.start()
}

runInThread { println("Hi"); Thread.sleep(1000); println("Bye") }

在例如:

def until(condition: => Boolean) (block: => Unit) {
  if (!condition) {
    block
    until(condition) (block)
  }
}

var x = 10
until (x == 0) {
  x -= 1
  println(x)
}

这样的函数参数叫做换名调用参数。和常规的参数不同,函数在被调用时,参数表达式不会被求值。


return表达式

在Scala中,函数的返回值就是函数体的值,即最后一个表达式的值。所有不需要使用return语句返回函数值。但是,你可以用return来从一个匿名函数中返回值给包含这个匿名函数的带名函数:

def indexOf(str: String, ch: Char): Int = {
  var i = 0
  until (i == str.length) {
    if (str(i) == ch) return i
    i += 1
  }
  return -1
}

如果在带名函数中使用return语句,需要给出它的返回类型。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容