Scala学习(十二)高阶函数

1.作为值的函数

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

import scala.math._

val num = 3.14

val fun = ceil _

这段代码将num设为3.14, fun设为ceil函数。

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

02bbc0dd93450fb0c8ad2abfddb202651f7.jpg

怎么使用函数:

  • 调用它
  • 传递它,存放在变量中,或者作为参数传递给另一个函数

以下是如何调用存放在fun中的函数:

fun(num) // fun是一个包含函数的变量,而不是一个固定的函数

以下是如何将fun传递给另一个函数:

Array(3.14, 1.42, 2.0).map(fun) // 将fun传递给另一个函数, Array(4.0, 2.0, 2.0)

map方法接收一个函数参数,将它应用到数组中的所有值,然后返回结果的数组。

2.匿名函数

在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((_ * 3) //或者这样,最简形式
Array(3.14, 1.42. 2.0).map{ (x: Double) => 3 * x } // 也可以使用花括号
Array(3.14, 1.42. 2.0) map { (x: Double) => 3 * x } // 使用中置表示法

练习:定义一个函数,入参4个,前两个数Int数字,后两个是函数,当第一个入参大于0时调用第一个函数参数,否则调用第二个函数参数

def call(a:Int, b:Int, f1:(Int,Int)=>Int, f2:(Int,Int)=>Int) = {
    if(a > 0) f1(a,b) else f2(a,b)
}

def add(a:Int, b:Int) = a + b
def sub(a:Int, b:Int) = a - b

val f1 = add _
val f2 = sub _

//可以采用如下方式调用call函数

call(1, 2, f1, f2)
call(1, 2, add _, sub _)
call(1, 2, add, sub)
call(1,2,(a:Int,b:Int)=>a+b,(a:Int,b:Int)=>a-b)

 

3.带函数参数的函数

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)

4.参数类型推断

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

5.一些有用的高阶函数

(1 to 9).map(0.1 * _) // _应用于所有元素
(1 to 9).map("*" * _).foreach(println _) //打印一个三角形

在这里我们还用到了foreach,它和map很像,只不过他的函数并不返回任何值

filter方法输出所有匹配某个特定条件的元素。

(1 to 9).filter(_ % 2 == 0) // 将能被2整除的过滤出来,输出2,4,6,8

当然,这并不是得到该结果的最高效方式

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

等同于 1*2*3*4*5*6*7*8*9 或者更严格地说 ((((((((1*2)*3)*4)*5)*6)*7)*8)*9)// 排序

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

6.闭包

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

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存储的函数访问了它们作用于范围外的变量。

7.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 类型的函数了。

 

8.柯里化

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

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。

9.控制抽象

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

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)
}

10.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语句,需要给出它的返回类型。

注:如果异常在被送往待命函数值前,在一个try代码块中被捕获掉了,那么相应的值就不会返回。

转载于:https://my.oschina.net/u/3687664/blog/2236840

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值