Scala之高阶函数
2016-03-21 15:18
141 查看
在函数式编程语言中,函数是“头等公民”,可以像任何其他数据类型一样被传递和操作。因为Scala混合了面向对象和函数式的特性,所以对Scala来说,函数是“头等公民”。
val fun = ceil _ // _将ceil方法转成了函数
在Scala中,无法直接操纵方法,只能直接操纵函数,所以需要使用_。
fun的类型是(Double)=>Double,意为接受Double参数并返回Double的函数。能够对fun做的有:调用,传递。
val num = 3.14
fun(num) // 返回4.0,调用fun
Array(3.14, 1.42, 2.0).map(fun) //返回Array(4.0, 2.0, 2.0),将fun作为变量传递
(x: Double) => 3 * x // 该匿名函数将传给它的参数乘3
可以将匿名函数赋值给变量,也可以当参数传递。
def valueAtOneQuarter(f: (Double) => Double) = f(0.25)
该函数的类型是:
((Double)
=>
Double)
=>
Double。
还有可以返回一个函数的函数:
def mulBy(factor: Double) = (x: Double) => factor * x
// mulBy可以产出任何两个数相乘的函数
val quintuple = mulBy(5) // (x: Double) => 5 * x
quintuple(20) // 5 * 20
这样接受函数参数,或者是返回函数的函数,被称为高阶函数(higher-order function)。
def
valueAtOneQuarter(f:
(Double)
=>
Double)
= f(0.25),因为已知参数的类型,所以Scala会尽可能推断出类型,在传入参数时,可以省掉一些内容。
valueAtOneQuarter((x: Double) => 3 * x) // 完整写法
valueAtOneQuarter((x) => 3 * x) // 已知参数类型,可以省掉Double
valueAtOneQuarter(x => 3 * x) // 只有一个参数时,可以省去()
valueAtOneQuarter(3 * _) // 参数只在右侧出现一次,可以用_替换
举例,点击一个按钮时,增加一个计数器:
var counter = 0
val button = new JButton("Increment")
button.addActionListener(new ActionListener {
override def actionPerformed(event: ActionEvent) {
count += 1
}
})
这是非常常见的,给按钮添加监听器的代码。其实只要给addActionListener传一个函数参数,也就能够实现一样的功能了。
button.addActionListener((event: ActionEvent) => counter += 1)
为了使这个语法真的生效,需要提供一个隐式转换。隐式转换将在21章详述。下面是简单的示例:
implicit def makeAction(action: (ActionEvent) => Unit) =
new ActionListener {
override def actionPerformed(event: ActionEvent) { action(event) }
}
将这个函数和界面代码放在一起,就可以在所有预期ActionListener对象的地方,传入(ActionEvent)=>Unit函数参数。
从上面的代码可以看出,隐式转换就是将一种类型自动转换成另外一种类型,是个函数。因为在Scala中,函数是头等公民,所以隐式转换的作用也大大放大了。
柯里化指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数作为参数的函数。
def mulOneAtATime(x: Int) = (y: Int) => x * y
// 计算两个数的乘积
mulOneAtATime(6)(7)
// 多参数的写法
def mul(x: Int, y: Int) = x * y
mulOneAtATime(6)返回的是函数(y: Int)=>6*y,再将这个函数应用到7,最终得到结果。
柯里化函数可以在Scala中简写:
def mulOneAtATime(x: Int)(y: Int) = x * y
多参数是个虚饰,不是编程语言的根本性的特质。
可以利用柯里化把某个函数参数单独拎出来,提供更多用于类型推断的信息。
val a = Array("Hello", "World")
val b = Array("hello", "world")
a.corresponds(b)(_.equalsIgnoreCase(_))
corresponds的类型声明如下:
def corresponds[B](that: GenSeq[B])(p: (T, B) ⇒ Boolean): Boolean
方法有两个参数,that序列和f函数,其中f函数有两个参数,第二个参数类型是与that序列一致的。因为使用了柯里化,我们可以省去第二个参数中B的类型,因为从that序列中推断出B的类型。于是,_equalsIgnoreCase(_)这个简写就符合参数的要求了。
def runInThread(block: () => Unit) {
new Thread {
override def run() { block() }
}.start()
}
// 调用
runInThread { () => println("Hi"); Thread.sleep(10000); println("Bye") }
可以去掉调用中的()=>,在参数声明和调用该函数参数的地方略去(),保留=>。
def runInThread(block: => Unit) {
new Thread {
override def run () { block }
}.start()
}
// 调用
runInThread { println("Hi"); Thread.sleep(10000); println("Bye") }
Scala程序员可以构建控制抽象:看上去像是编程语言关键字的函数。
def until(condition: => Boolean)(block: => Unit) {
if (!condition) {
block
until(condition)(block)
}
}
// 使用
var x = 10
until (x == 0) {
x -= 1
println(x)
}
这样的函数参数专业术语叫做换名调用参数(常规的参数叫换值调用参数)。函数在调用时,换名调用参数的表达式不会被求值,表达式会被当做参数传递下去。
def indexOf(str: String, ch: Char): Int = {
var i = 0
util (i == str.length) {
if ( str(i) == str.length) return i
i += 1
}
return -1
}
在这里,util这个抽象控制中的return语句,会使外部的带名函数indexOf终止并且返回i的值。
这里控制流程的实现依赖于在匿名函数中return表达式抛出的特殊异常。如果这个异常在被送往带名函数前被捕获,那么就无法为带名函数返回值了,这一点需要注意。
作为值的函数
import scala.math._val fun = ceil _ // _将ceil方法转成了函数
1 2 3 | import scala.math._ val fun = ceil _ // _将ceil方法转成了函数 |
fun的类型是(Double)=>Double,意为接受Double参数并返回Double的函数。能够对fun做的有:调用,传递。
val num = 3.14
fun(num) // 返回4.0,调用fun
Array(3.14, 1.42, 2.0).map(fun) //返回Array(4.0, 2.0, 2.0),将fun作为变量传递
1 2 3 4 5 | val num = 3.14 fun(num) // 返回4.0,调用fun Array(3.14, 1.42, 2.0).map(fun) //返回Array(4.0, 2.0, 2.0),将fun作为变量传递 |
匿名函数
函数不一定需要名称:(x: Double) => 3 * x // 该匿名函数将传给它的参数乘3
1 | (x: Double) => 3 * x // 该匿名函数将传给它的参数乘3 |
带函数参数的函数
如何实现一个接受另一个函数为参数的函数:def valueAtOneQuarter(f: (Double) => Double) = f(0.25)
1 | def valueAtOneQuarter(f: (Double) => Double) = f(0.25) |
((Double)
=>
Double)
=>
Double。
还有可以返回一个函数的函数:
def mulBy(factor: Double) = (x: Double) => factor * x
// mulBy可以产出任何两个数相乘的函数
val quintuple = mulBy(5) // (x: Double) => 5 * x
quintuple(20) // 5 * 20
1 2 3 4 5 | def mulBy(factor: Double) = (x: Double) => factor * x // mulBy可以产出任何两个数相乘的函数 val quintuple = mulBy(5) // (x: Double) => 5 * x quintuple(20) // 5 * 20 |
参数(类型)推断
前面有定义高阶函数def
valueAtOneQuarter(f:
(Double)
=>
Double)
= f(0.25),因为已知参数的类型,所以Scala会尽可能推断出类型,在传入参数时,可以省掉一些内容。
valueAtOneQuarter((x: Double) => 3 * x) // 完整写法
valueAtOneQuarter((x) => 3 * x) // 已知参数类型,可以省掉Double
valueAtOneQuarter(x => 3 * x) // 只有一个参数时,可以省去()
valueAtOneQuarter(3 * _) // 参数只在右侧出现一次,可以用_替换
1 2 3 4 | valueAtOneQuarter((x: Double) => 3 * x) // 完整写法 valueAtOneQuarter((x) => 3 * x) // 已知参数类型,可以省掉Double valueAtOneQuarter(x => 3 * x) // 只有一个参数时,可以省去() valueAtOneQuarter(3 * _) // 参数只在右侧出现一次,可以用_替换 |
闭包
闭包(closure)这个概念,虽然差不多能懂,但解释不清楚的感觉,参考一下闭包的维基百科。SAM转换
在Scala中,要某个函数做某件事时,会传一个函数参数给它。而在Java中,并不支持传送参数。通常Java的实现方式是将动作放在一个实现某接口的类中,然后将该类的一个实例传递给另一个方法。很多时候,这些接口只有单个抽象方法(single abstract method),在Java中被称为SAM类型。举例,点击一个按钮时,增加一个计数器:
var counter = 0
val button = new JButton("Increment")
button.addActionListener(new ActionListener {
override def actionPerformed(event: ActionEvent) {
count += 1
}
})
1 2 3 4 5 6 7 8 | var counter = 0 val button = new JButton("Increment") button.addActionListener(new ActionListener { override def actionPerformed(event: ActionEvent) { count += 1 } }) |
button.addActionListener((event: ActionEvent) => counter += 1)
1 | button.addActionListener((event: ActionEvent) => counter += 1) |
implicit def makeAction(action: (ActionEvent) => Unit) =
new ActionListener {
override def actionPerformed(event: ActionEvent) { action(event) }
}
1 2 3 4 | implicit def makeAction(action: (ActionEvent) => Unit) = new ActionListener { override def actionPerformed(event: ActionEvent) { action(event) } } |
从上面的代码可以看出,隐式转换就是将一种类型自动转换成另外一种类型,是个函数。因为在Scala中,函数是头等公民,所以隐式转换的作用也大大放大了。
柯里化(Currying)
柯里化的概念也请参考维基百科。柯里化指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数作为参数的函数。
def mulOneAtATime(x: Int) = (y: Int) => x * y
// 计算两个数的乘积
mulOneAtATime(6)(7)
// 多参数的写法
def mul(x: Int, y: Int) = x * y
1 2 3 4 5 6 | def mulOneAtATime(x: Int) = (y: Int) => x * y // 计算两个数的乘积 mulOneAtATime(6)(7) // 多参数的写法 def mul(x: Int, y: Int) = x * y |
柯里化函数可以在Scala中简写:
def mulOneAtATime(x: Int)(y: Int) = x * y
1 | def mulOneAtATime(x: Int)(y: Int) = x * y |
可以利用柯里化把某个函数参数单独拎出来,提供更多用于类型推断的信息。
val a = Array("Hello", "World")
val b = Array("hello", "world")
a.corresponds(b)(_.equalsIgnoreCase(_))
1 2 3 | val a = Array("Hello", "World") val b = Array("hello", "world") a.corresponds(b)(_.equalsIgnoreCase(_)) |
def corresponds[B](that: GenSeq[B])(p: (T, B) ⇒ Boolean): Boolean
1 | def corresponds[B](that: GenSeq[B])(p: (T, B) ⇒ Boolean): Boolean |
控制抽象
Scala中,可以将一系列语句归组成不带参数也没有返回值的函数。def runInThread(block: () => Unit) {
new Thread {
override def run() { block() }
}.start()
}
// 调用
runInThread { () => println("Hi"); Thread.sleep(10000); println("Bye") }
1 2 3 4 5 6 7 8 | def runInThread(block: () => Unit) { new Thread { override def run() { block() } }.start() } // 调用 runInThread { () => println("Hi"); Thread.sleep(10000); println("Bye") } |
def runInThread(block: => Unit) {
new Thread {
override def run () { block }
}.start()
}
// 调用
runInThread { println("Hi"); Thread.sleep(10000); println("Bye") }
1 2 3 4 5 6 7 8 | def runInThread(block: => Unit) { new Thread { override def run () { block } }.start() } // 调用 runInThread { println("Hi"); Thread.sleep(10000); println("Bye") } |
def until(condition: => Boolean)(block: => Unit) {
if (!condition) {
block
until(condition)(block)
}
}
// 使用
var x = 10
until (x == 0) {
x -= 1
println(x)
}
1 2 3 4 5 6 7 8 9 10 11 12 13 | def until(condition: => Boolean)(block: => Unit) { if (!condition) { block until(condition)(block) } } // 使用 var x = 10 until (x == 0) { x -= 1 println(x) } |
return表达式
一般不需要使用return来返回函数值。但是return可以用来从一个匿名函数中返回值给包含这个匿名函数的带名函数,对于控制抽象来说是很有用的。如果要在带名参数中使用return,需要在定义中给出返回类型。def indexOf(str: String, ch: Char): Int = {
var i = 0
util (i == str.length) {
if ( str(i) == str.length) return i
i += 1
}
return -1
}
1 2 3 4 5 6 7 8 | def indexOf(str: String, ch: Char): Int = { var i = 0 util (i == str.length) { if ( str(i) == str.length) return i i += 1 } return -1 } |
这里控制流程的实现依赖于在匿名函数中return表达式抛出的特殊异常。如果这个异常在被送往带名函数前被捕获,那么就无法为带名函数返回值了,这一点需要注意。
相关文章推荐
- expect的安装
- Ubuntu 系统中设置环境变量 PATH 的方法
- 《CLR Via C#》使用CSC.exe进行单文件的编译
- Zookeeper学习(十一):ZooKeeper 实现命名服务
- Google cardBoard Android API (十):HeadMountedDisplay
- java基础经典练习题
- 关于UIMenuController的用法例子
- java线程研究
- Android学习之解决ScrollView嵌套ListView显示的错误
- webview加载html格式的文本出现乱码
- 作业:用HTML制作邮箱登陆界面
- 批处理学习:for语句详解【经典】(转)
- iOS证书失效
- 【Codeforces Round 263 (Div 2)D】【树形DP】Appleman and Tree 树上割k个黑点为k块的方案数
- Gradle使用小结
- Unique Binary Search Trees II [Leetcode 解题报告]
- Android实现简单的分批加载ListView
- 3.3.Android控件架构与自定义控件详解之View的绘制
- 集合-1
- serv-u使用WINDOWS AD域用户验证的尝试