Scala学习(十二)高阶函数

1.做为值的函数

在Scala中,你能够在变量中存放函数:数组

import scala.math._

val num = 3.14

val fun = ceil _

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

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

怎么使用函数:函数

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

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

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

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

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

map方法接收一个函数参数,将它应用到数组中的全部值,而后返回结果的数组。scala

2.匿名函数

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

(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时调用第一个函数参数,不然调用第二个函数参数orm

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代码块中被捕获掉了,那么相应的值就不会返回。

相关文章
相关标签/搜索