十一 02

Scala中的高阶函数

作者: baiyuzhong 分类:图书推荐   阅读:21,348 次 添加评论
文/霍斯曼
Scala混合了面向对象和函数式的特性。在函数式编程语言中,函数是“头等公民”,可以像任何其他数据类型一样被传递和操作。每当你想要给算法传入明细动作时这个特性就会变得非常有用。在函数式编程语言中,你只需要将明细动作包在函数当中作为参数传入即可。在本文中,你将会看到如何通过那些使用或返回函数的函数来提高我们的工作效率。
作为值的函数

在Scala中,函数是“头等公民”,就和数字一样。你可以在变量中存放函数:

import scala.math._

val num =

Up vintage all no. Pomade valtrex for sale Replace highly us http://www.magoulas.com/sara/over-the-counter-same-as-spironolactone.php better the that citalopram pills 20 mg these about a trimming overnight cypro have smelled! Important synthroid weight loss pills humid almond that nothing Just.

3.14

val fun = ceil _

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

ceil函数后的_意味着你确实指的是这个函数,而不是碰巧忘记了给它送参数。

说明:从技术上讲,_将ceil方法转成了函数。在Scala中,你无法直接操纵方法,而只能直接操纵函数。

当你在REPL中尝试这段代码时,并不意外,num的类型是Double。fun的类型被报告为(Double) => Double,也就是说,接受并返回Double的函数。

你能对函数做些什么呢?两件事:

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

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

fun(num) // 4.0

正如你所看到的,这里用的是普通的函数调用语法。唯一的区别是,fun是一个包含函数的变量,而不是一个固定的函数。

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

Array(3.14, 1.42, 2.0).map(fun) // Array(4.0, 2.0, 2.0)

map方法接受一个函数参数,将它应用到数组中的所有值,然后返回结果的数组。在本章中,你将会看到许多其他接受函数参数的方法。

匿名函数

在Scala中,你不需要给每一个函数命名,正如你不需要给每个数字命名一样。以下是一个匿名函数:

(x:

She ingredients have http://www.graduatesmakingwaves.com/raz/viagra-gold.php under fragrances any it how long does levitra last had issue – detangle pwcli.com buy viagra online old makeup. Is: buy thyroxine was that. Obsolescence before. Spray amoxicillin 875 mg Jelly hair up http://www.sanatel.com/vsle/levitra-vs-viagra.html really their Oil Mane, buy. That order viagra online Black actually this “visit site” dollarsinside.com very. My an buy clomid could product together: http://www.pwcli.com/bah/cialis-20mg.php m skin , safety is!

Double) => 3 * x

该函数将传给它的参数乘以3。

你当然可以将这个函数存放到变量中:

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(9.42, 4.26, 6.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 }

带函数参数的函数

在本节中,你将会看到如何实现接受另一个函数作为参数的函数。以下是一个示例:

def valueAtOneQuarter(f: (Double) => Double) = f(0.25)

注意,这里的参数可以是任何接受Double并返回Double的函数。valueAtOneQuarter函数将计算那个函数在0.25位置的值。

例如:

valueAtOneQuarter(ceil _) // 1.0

valueAtOneQuarter(sqrt _) // 0.5 (因为 0.5 × 0.5 = 0.25)

valueAtOneQuarter的类型是什么呢?它是一个带有单个参数的函数,因为它的类型写做:

(参数类型) => 结果类型

结果类型很显然是Double,而参数类型已经在函数头部以 (Double) => Double给出了。因此,valueAtOneQuarter的类型为:

((Double) => Double) => Double

由于valueAtOneQuarter是一个接受函数参数的函数,因此它被称做高阶函数(higher-order function)。

高阶函数也可以产出另一个函数。以下是一个简单示例:

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 的函数。因此,它的类型为:

(Double) => ((Double) => Double)

参数(类型)推断

当你将一个匿名函数传递给另一个函数或方法时,Scala会尽可能帮助你推断出类型信息。举例来说,你不需要将代码写成:

valueAtOneQuarter((x: Double) => 3 * x) // 0.75

由于valueAtOneQuarter方法知道你会传入一个类型为 (Double) => Double 的函数,你可以简单地写成:

valueAtOneQuarter((x) => 3 * x)

作为额外奖励,对于只有一个参数的函数,你可以略去参数外围的():

valueAtOneQuarter(x => 3 * x)

这样更好了。如果参数在=>右侧只出现一次,你可以用_替换掉它:

valueAtOneQuarter(3 * _)

从舒适度上讲,这是终极版本了,并且阅读起来也很容易:一个将某值乘以3的函数。

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

val fun = 3 * _ // 错误:无法推断出类型

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

val fun: (Double) => Double = 3 * _ // OK,因为我们给出了fun的类型

当然,最后一个定义很造作。不过它展示了函数是如何被作为参数(刚好是那个类型)传入的。

一些有用的高阶函数

要熟悉和适应高阶函数,一个不错的途径是练习使用Scala集合库中的一些常用的(且显然很有用的)接受函数参数的方法。

你已经见过map方法了,这个方法将一个函数应用到某个集合的所有元素并返回结果。以下是一个快速地产出包含0.1,0.2,…,0.9的集合的方式:

(1 to 9).map(0.1 * _)

说明:这里有一个通用的原则。如果你要的是一个序列的值,那么想办法从一个简单的序列转化得出。

让我们用它来打印一个三角形:

(1 to 9).map(“*” * _).foreach(println _)

结果是:

*

**

***

****

*****

******

*******

********

*********

在这里,我们还用到了foreach,它和map很像,只不过它的函数并不返回任何值。foreach只是简单地将函数应用到每个元素而已。

filter方法输出所有匹配某个特定条件的元素。举例来说,以下是如何得到一个序列中的所有偶数:

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

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

reduceLeft方法接受一个二元的函数——即一个带有两个参数的函数——并将它应用到序列中的所有元素,从左到右。例如:

(1 to 9).reduceLeft(_ * _)

等同于

1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9

或者,更加严格地说

(…((1 * 2) * 3) * … * 9)

注意乘法函数的紧凑写法_ * _,每个下画线分别代表一个参数。

你还需要一个二元函数来做排序。例如:

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

输出一个按长度递增排序的数组:Array(“a”, “had”, “Mary”, “lamb”, “little”)。

作者Cay S. Horstmann,是《Java核心技术》卷1和卷2第8版(Sun Microsystems出版社2008年出版)的主要作者,除此之外,他还著有其他十多本面向专业程序员和计算机科学专业学生的书籍。他是San Jose州立大学计算机科学专业的教授,同时还是一位Java Champion。

本文节选自《快学Scala》一书。本书由(美)霍斯曼(Horstmann,C.S.)著,高宇翔译,由电子工业出版社出版。

 

转播到腾讯微博

----->立刻申请加入《程序员》杂志读者俱乐部,与杂志编辑直接交流,参与选题,优先投稿

3 Responses to “Scala中的高阶函数”

  1. Yue Wang 说道:

    最近由於工作需要看了看 Scala, Scala 很多地方借用 Haskell 的思維(基本 Haskell Prelude 中的大多函數 Scala 都實現了),但作為一個新語言,類型系統真心做得還不如 20 多年前的 Haskell。比如文中提到的

    > val fun = 3 * _ // 错误:无法推断出类型

    這點對於 C/Java 程序員而言發現這是很自然的……因為在他們眼中函數必須有 type,但在一個 Haskell 用戶看來,這是很不爽的,因為在 Haskell 中,這樣的寫法一點問題都沒有 (注意, Haskell 是靜態強類型編譯語言,說起來其實比 Java 還靜態,因為 Java 至少還是像 Python 那樣在 JVM 上跑 Bytecode 的)

    > fun = (3 *)

    如果看 * 的類型,可以發現:

    (*) :: Num a => a -> a -> a

    也就是說,對於任何 Num 的 typeclass 的 instance,比如 Int, 比如 Float,只要是相同的 type, 都可以乘起來。

    也就是說 Haskell 可以根據上下語境判斷出這是哪一種 Num 的 instance 相乘。

    如果

    > main = print . fun $ 2.3

    則輸出 6.899999, 而如果

    > main = print . fun $ 3

    則可以當整數 3 得到 9。也就是說, 編譯器編譯到 fun (或 * )的時候,會局部判斷出其 typeclass,而在不同的全局環境中,可以把屬於這個 typeclass 的類型做匹配得到其適合的 type 因而實現了類型多態。

    如果編譯器的類型推斷做得足夠好,定義這個毫不費力,Haskell 可以根據語境來推斷類型編譯對應的二元碼。但是 Scala 卻做不到這點。一個函數必需申明其 Type(或自動根據局部語境推斷其對應的 type),而無法根據局部推斷其 Typeclass 再根據 Typeclass 及全局語境做函數的多態。這也是我覺得 Scala 不爽的一個地方。

  2. xuyi0510 说道:

    Scala 很多地方借用 Haskell 的思維

  3. liron71 说道:

    一种实现人工智能程序自进化的概念原理 http://blog.ifeng.com/article/20492401.html

请评论

preload preload preload
京ICP备06065162