Scala学习——高阶函数
2015-11-02 16:50
309 查看
原文发表于:http://nerd-is.in/2013-09/scala-learning-higher-order-functions/
在函数式编程语言中,函数是“头等公民”,可以像任何其他数据类型一样被传递和操作。因为Scala混合了面向对象和函数式的特性,所以对Scala来说,函数是“头等公民”。
在Scala中,无法直接操纵方法,只能直接操纵函数,所以需要使用_。fun的类型是(Double)=>Double,意为接受Double参数并返回Double的函数。能够对fun做的有:调用,传递。
函数不一定需要名称:
可以将匿名函数赋值给变量,也可以当参数传递。
该函数的类型是: ((Double) => Double) => Double。
还有可以返回一个函数的函数:
这样接受函数参数,或者是返回函数的函数,被称为高阶函数(higher-order function)。
闭包(closure)这个概念,虽然差不多能懂,但解释不清楚的感觉,参考一下闭包的维基百科。
在Scala中,要某个函数做某件事时,会传一个函数参数给它。而在Java中,并不支持传送参数。通常Java的实现方式是将动作放在一个实现某接口的类中,然后将该类的一个实例传递给另一个方法。很多时候,这些接口只有单个抽象方法(single abstract method),在Java中被称为SAM类型。
举例,点击一个按钮时,增加一个计数器:
这是非常常见的,给按钮添加监听器的代码。其实只要给addActionListener传一个函数参数,也就能够实现一样的功能了。
为了使这个语法真的生效,需要提供一个隐式转换。隐式转换将在21章详述。下面是简单的示例:
将这个函数和界面代码放在一起,就可以在所有预期ActionListener对象的地方,传入(ActionEvent)=>Unit函数参数。从上面的代码可以看出,隐式转换就是将一种类型自动转换成另外一种类型,是个函数。因为在Scala中,函数是头等公民,所以隐式转换的作用也大大放大了。
mulOneAtATime(6)返回的是函数(y: Int)=>6*y,再将这个函数应用到7,最终得到结果。
柯里化函数可以在Scala中简写:
多参数是个虚饰,不是编程语言的根本性的特质。可以利用柯里化把某个函数参数单独拎出来,提供更多用于类型推断的信息。
corresponds的类型声明如下:
方法有两个参数,that序列和f函数,其中f函数有两个参数,第二个参数类型是与that序列一致的。因为使用了柯里化,我们可以省去第二个参数中B的类型,因为从that序列中推断出B的类型。于是,_equalsIgnoreCase(_)这个简写就符合参数的要求了。
可以去掉调用中的()=>,在参数声明和调用该函数参数的地方略去(),保留=>。
Scala程序员可以构建控制抽象:看上去像是编程语言关键字的函数。
这样的函数参数专业术语叫做换名调用参数(常规的参数叫换值调用参数)。函数在调用时,换名调用参数的表达式不会被求值,表达式会被当做参数传递下去。
一般不需要使用return来返回函数值。但是return可以用来从一个匿名函数中返回值给包含这个匿名函数的带名函数,对于控制抽象来说是很有用的。如果要在带名参数中使用return,需要在定义中给出返回类型。
在这里,util这个抽象控制中的return语句,会使外部的带名函数indexOf终止并且返回i的值。
这里控制流程的实现依赖于在匿名函数中return表达式抛出的特殊异常。如果这个异常在被送往带名函数前被捕获,那么就无法为带名函数返回值了,这一点需要注意。
在函数式编程语言中,函数是“头等公民”,可以像任何其他数据类型一样被传递和操作。因为Scala混合了面向对象和函数式的特性,所以对Scala来说,函数是“头等公民”。
作为值的函数
123 | import scala.math._ val fun = ceil _ // _将ceil方法转成了函数 |
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作为变量传递 |
匿名函数
函数不一定需要名称:1 | (x: Double) => 3 * x // 该匿名函数将传给它的参数乘3 |
带函数参数的函数
如何实现一个接受另一个函数为参数的函数:1 | def valueAtOneQuarter(f: (Double) => Double) = f(0.25) |
还有可以返回一个函数的函数:
12345 | def mulBy(factor: Double) = (x: Double) => factor * x // mulBy可以产出任何两个数相乘的函数val quintuple = mulBy(5) // (x: Double) => 5 * xquintuple(20) // 5 * 20 |
参数(类型)推断
前面有定义高阶函数 def valueAtOneQuarter(f: (Double) => Double) = f(0.25),因为已知参数的类型,所以Scala会尽可能推断出类型,在传入参数时,可以省掉一些内容。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类型。举例,点击一个按钮时,增加一个计数器:
12345678 | var counter = 0 val button = new JButton("Increment")button.addActionListener(new ActionListener { override def actionPerformed(event: ActionEvent) { count += 1 }}) |
1 | button.addActionListener((event: ActionEvent) => counter += 1) |
1234 | implicit def makeAction(action: (ActionEvent) => Unit) = new ActionListener { override def actionPerformed(event: ActionEvent) { action(event) } } |
柯里化(Currying)
柯里化的概念也请参考维基百科。柯里化指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数作为参数的函数。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中简写:
1 | def mulOneAtATime(x: Int)(y: Int) = x * y |
1 2 3 | val a = Array("Hello", "World") val b = Array("hello", "world") a.corresponds(b)(_.equalsIgnoreCase(_)) |
1 | def corresponds[B](that: GenSeq[B])(p: (T, B) ⇒ Boolean): Boolean |
控制抽象
Scala中,可以将一系列语句归组成不带参数也没有返回值的函数。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") } |
12345678 | 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 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,需要在定义中给出返回类型。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表达式抛出的特殊异常。如果这个异常在被送往带名函数前被捕获,那么就无法为带名函数返回值了,这一点需要注意。
相关文章推荐
- jquery
- 升级Capitan 10.11以后CocoaPod 无效解决办法
- 第七周 队列数组
- 使用asmca无法启动图形界面
- 第九周项目1 猴子选大王(数组版)
- Android的分类ListView
- java多线程单例模式
- 关于OpenCV的那些事——画AR物体(单目控制)
- 对商品类别进行聚类
- 使用JDBC处理mysql
- css sprites 多张图片整合在一张图片上
- 服务器监控客户端系统状态3.0
- 【POI2012】【BZOJ2795】A Horrible Poem
- Ajax快速学习笔记
- 第十周--项目2二叉树遍历递归算法
- 第9周项目3 稀疏矩阵的三元组表示的实现与应用(2)
- 第九周 项目3-稀疏矩阵的三元表示的实现及应用(2)
- 设置unity 编译文件到android项目
- AFNetworking 需要导入的框架
- 网上看到了一些ios面试题,看着解答解答