热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Scala学习笔记一些不错的学习材料函数编程的历史八卦

参考这篇文章:http:www.ibm.comdeveloperworkscnjavaj-lo-funinscala1这也是一个系列严格意义上的编程范式分为ÿ

参考这篇文章:

http://www.ibm.com/developerworks/cn/java/j-lo-funinscala1/

这也是一个系列

 

严格意义上的编程范式分为:命令式编程(Imperative Programming)、函数式编程(Functional Programming)和逻辑式编程(Logic Programming)

 

使用递归的方式去思考

清单 1. 数列求和

//xs.head 返回列表里的头元素,即第一个元素//xs.tail 返回除头元素外的剩余元素组成的列表def sum(xs: List[Int]): Int = if (xs.isEmpty) 0 else xs.head + sum(xs.tail)

清单 2. 求最大值

def max(xs: List[Int]): Int = { if (xs.isEmpty) throw new java.util.NoSuchElementException if (xs.size == 1) xs.head else if (xs.head > max(xs.tail)) xs.head else max(xs.tail) }

清单 3. 反转字符串

def reverse(xs: String): String = if (xs.length == 1) xs else reverse(xs.tail) + xs.head

清单 4. 快速排序

def quickSort(xs: List[Int]): List[Int] = { if (xs.isEmpty) xs else quickSort(xs.filter(x=>xx>xs.head)) }

习惯于命令式编程范式的程序员还有一个担忧:相比循环,递归不是存在效率问题吗?每一次递归调用,都会分配一个新的函数栈,如果递归嵌套很深,容易出现栈溢出的问题。比如下面计算阶乘的递归程序:

清单 5. 递归求阶乘

def factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n - 1)

当 n很大时,函数栈将很快被耗尽。然而尾递归能帮我们解决这个问题,所谓尾递归是指在函数调用的最后一步,只调用该递归函数本身,此时,由于无需记住其他变量,当前的函数栈可以被重复使用。上面的程序只需稍微改造一下,既可以变成尾递归式的程序,在效率上,和循环是等价的。

注:C++编译器对尾递归也进行了优化,但是编译器选项默认不打开,需要主动打开;Java没有对尾递归做优化。

清单 6. 尾递归求阶乘

def factorial(n: Int): Int = { @tailrec def loop(acc: Int, n: Int): Int = if (n == 0) acc else loop(n * acc, n - 1) loop(1, n) }

在上面的程序中,我们在阶乘函数内部定义了一个新的递归函数,该函数最后一步要么返回结果,要么调用该递归函数本身,所以这是一个尾递归函数。该函数多出一个变量 acc,每次递归调用都会更新该变量,直到递归边界条件满足时返回该值,即为最后的计算结果。这是一种通用的将非尾递归函数转化为尾递归函数的方法,大家可多加练习,掌握这一方法。对于尾递归,Scala 语言特别增加了一个注释 @tailrec,该注释可以确保程序员写出的程序是正确的尾递归程序,如果由于疏忽大意,写出的不是一个尾递归程序,则编译器会报告一个编译错误,提醒程序员修改自己的代码。

把上面的解法写进程序里就是:

package com.spark.myimport scala.annotation.tailrecobject Hello {def main(args: Array[String]): Unit = {println("hello world")val ret = factorial(3)println("ret: %d".format(ret))}def factorial(n: Int): Int = {@tailrecdef loop(acc: Int, n: Int): Int =if (n == 0) acc else loop(n * acc, n - 1)loop(1, n)}}

运行结果:

hello world
ret:
6Process finished with exit code 0

一道面试题

也许有的读者看了上面的例子后,还是感到不能信服:虽然使用递归会让程序变得简洁易懂,但我用循环也一样可以实现,大不了多几行代码而已,而且我还不用知道什么尾递归,写出的程序就是效率最高的。那我们一起来看看下面这个问题:有趣的零钱兑换问题。题目大致如下:

假设某国的货币有若干面值,现给一张大面值的货币要兑换成零钱,问有多少种兑换方式。

 

解答:第一种找零方式总共有 countChange(money, coins.tail)种,第二种找零方式等价为对于 money – conins.head进行同样的兑换,则这种兑换方式有 countChange(money - coins.head, coins)种,两者之和即为所有的零钱兑换方式。

写成代码:

package com.spark.myimport scala.annotation.tailrecobject Hello {def main(args: Array[String]): Unit &#61; {println("hello world")val coins &#61; List(1, 2, 5)val money &#61; 10val ret &#61; count(money, coins)println("ret: %d".format(ret))}def count(money: Int, coins: List[Int]): Int &#61; {if (money &#61;&#61; 0)1else if (coins.size &#61;&#61; 0 || money <0)0elsecount(money, coins.tail) &#43; count(money - coins.head, coins)}}

运行&#xff1a;

hello world
ret:
10Process finished with exit code 0

但是注意&#xff0c;开始的时候&#xff0c;我在count前面加上了 &#64;tailrec&#xff0c;但是报错&#xff0c;说这个不是尾递归。还不清楚&#xff0c;怎么把这个改成尾递归。

结束语

事实上&#xff0c;在 Haskell 语言中&#xff0c;不存在 while、for 等命令式编程语言中必不可少的循环控制语句&#xff0c;Haskell 强迫程序员使用递归等函数式编程的思维去解决问题。作者也鼓励大家以后碰到问题时&#xff0c;先考虑有没有好的递归的方式实现&#xff0c;看看是否会为我们关于编程的理解带来新的思考。

而Scala融合了Functional programming 和 OO programming&#xff0c;有class和Object.

 

开始看第二篇了&#xff1a;

http://www.ibm.com/developerworks/cn/java/j-lo-funinscala2/

Scala 语法非常简洁&#xff0c;拥有其他语言编程经验的程序员很容易读懂 Scala 代码。现在我们将回过头来&#xff0c;从基本的语法开始学习 Scala 语言。大家会发现 Scala 语言异常精炼&#xff0c;实现同样功能的程序&#xff0c;在代码量上&#xff0c;使用 Scala 实现通常比 Java 实现少一半或者更多。短小精悍的代码常常意味着更易懂&#xff0c;更易维护。本文将为大家介绍 Scala 语言的基本语法&#xff0c;帮助大家写出自己的第一个 Scala 程序。

 

Scala 为定义变量提供了两种语法。

使用 val定义常量&#xff0c;一经定义后&#xff0c;该变量名不能被重新赋值。

使用 var定义变量&#xff0c;可被重新赋值。

在 Scala 中&#xff0c;鼓励使用 val&#xff0c;除非你有明确的需求使用 var。对于 Java 程序员来说&#xff0c;刚开始可能会觉得有违直觉&#xff0c;但习惯后你会发现&#xff0c;大多数场合下我们都不需要 var&#xff0c;一个可变的变量。

清单 1. 定义变量

val x &#61; 0 var y &#61; 1 y &#61; 2 // 给常量赋值会出现编译错误// x &#61; 3 // 显式指定变量类型val x1: Int &#61; 0 var y1: Int &#61; 0

定义变量时没有指定变量类型。这是否意味着 Scala 是和 Python 或者 Ruby 一样的动态类型语言呢&#xff1f;

恰恰相反&#xff0c;Scala 是严格意义上的静态类型语言&#xff0c;由于其采用了先进的类型推断&#xff08;Type Inference&#xff09;技术&#xff0c;程序员不需要在写程序时显式指定类型&#xff0c;编译器会根据上下文推断出类型信息。比如变量 x被赋值为 0&#xff0c;0 是一个整型&#xff0c;所以 x的类型被推断出为整型。当然&#xff0c;Scala 语言也允许显示指定类型&#xff0c;如变量 x1&#xff0c;y1的定义。一般情况下&#xff0c;我们应尽量使用 Scala 提供的类型推断系统使代码看上去更加简洁。

 

函数的定义也非常简单&#xff0c;使用关键字 def&#xff0c;后跟函数名和参数列表&#xff0c;如果不是递归函数可以选择省略函数返回类型。

Scala 还支持定义匿名函数&#xff0c;匿名函数由参数列表&#xff0c;箭头连接符和函数体组成。

清单 2. 定义函数

// 定义函数def square(x: Int): Int &#61; x * x // 如果不是递归函数&#xff0c;函数返回类型可省略def sum_of_square(x: Int, y: Int) &#61; square(x) &#43; square(y) sum_of_square(2, 3) // 定义匿名函数val cube &#61; (x: Int) &#61;> x * x *x cube(3) // 使用匿名函数&#xff0c;返回列表中的正数List(-2, -1, 0, 1, 2, 3).filter(x &#61;> x > 0)

Scala 中的一条语句其实是一个表达式&#xff0c;函数的执行过程就是对函数体内的表达式的求值过程&#xff0c;最后一条表达式的值就是函数的返回值。如果函数体只包含一条表达式&#xff0c;则可以省略 {}。其次&#xff0c;没有显式的 return语句&#xff0c;最后一条表达式的值会自动返回给函数的调用者。

 

和 Java 不同&#xff0c;在 Scala 中&#xff0c;函数内部还可以定义其他函数。比如上面的程序中&#xff0c;如果用户只对 sum_of_square 函数感兴趣&#xff0c;则我们可以将 square 函数定义为内部函数&#xff0c;实现细节的隐藏。

清单 3. 定义内部函数

def sum_of_square(x: Int, y: Int): Int &#61; { def square(x: Int) &#61; x * x square(x) &#43; square(y) }

 

流程控制语句

和 Java 中对应的条件判断语句不同&#xff0c;Scala 中的 if else是一个表达式&#xff0c;根据条件的不同返回相应分支上的值。比如下面例子中求绝对值的程序&#xff0c;由于 Scala 中的 if else是一个表达式&#xff0c;所以不用像 Java 那样显式使用 return返回相应的值。

清单 4. 使用 if else 表达式

def abs(n: Int): Int &#61; if (n > 0) n else -n

和 Java 一样&#xff0c;Scala 提供了用于循环的 while 语句&#xff0c;在下面的例子中&#xff0c;我们将借助 while 循环为整数列表求和。

清单 5. 使用 while 为列表求和

def sum(xs: List[Int]) &#61; { var total &#61; 0 var index &#61; 0 while (index < xs.size) { total &#43;&#61; xs(index) index &#43;&#61; 1 } total }

上述程序是习惯了 Java 或 C&#43;&#43; 的程序员想到的第一方案&#xff0c;但仔细观察会发现有几个问题&#xff1a;

首先&#xff0c;使用了 var定义变量&#xff0c;我们在前面说过&#xff0c;尽量避免使用 var

其次&#xff0c;这个程序太长了&#xff0c;第一次拿到这个程序的人需要对着程序仔细端详一会&#xff1a;程序首先定义了两个变量&#xff0c;并将其初始化为 0&#xff0c;然后在 index小于列表长度时执行循环&#xff0c;在循环体中&#xff0c;累加列表中的元素&#xff0c;并将 index加 1&#xff0c;最后返回最终的累加值。

直到这时&#xff0c;这个人才意识到这个程序是对一个数列求和。

 

让我们换个角度&#xff0c;尝试用递归的方式去思考这个问题&#xff0c;对一个数列的求和问题可以简化为该数列的第一个元素加上由后续元素组成的数列的和&#xff0c;依此类推&#xff0c;直到后续元素组成的数列为空返回 0。具体程序如下&#xff0c;使用递归&#xff0c;原来需要 9 行实现的程序现在只需要两行&#xff0c;而且程序逻辑看起来更清晰&#xff0c;更易懂。&#xff08;关于如何使用递归的方式去思考问题&#xff0c;请参考作者的另外一篇文章《使用递归的方式去思考》&#xff09;(已在本文的前半部分学习并引用)

清单 6. 使用递归对数列求和

//xs.head 返回列表里的头元素&#xff0c;即第一个元素
//xs.tail 返回除头元素外的剩余元素组成的列表def sum1(xs: List[Int]): Int &#61; if (xs.isEmpty) 0 else xs.head &#43; sum1(xs.tail)

有没有更简便的方式呢&#xff1f;答案是肯定的&#xff0c;我们可以使用列表内置的一些方法达到同样的效果&#xff1a;

xs.foldLeft(0)((x0, x) &#61;> x0 &#43; x)

该方法传入一个初始值 0&#xff0c;一个匿名函数&#xff0c;该匿名函数累加列表中的每一个元素&#xff0c;最终返回整个列表的和。使用上面的方法&#xff0c;我们甚至不需要定义额外的方法&#xff0c;就可以完成同样的操作。

事实上&#xff0c;List 已经为我们提供了 sum 方法&#xff0c;在实际应用中&#xff0c;我们应该使用该方法&#xff0c;而不是自己定义一个。作者只是希望通过上述例子&#xff0c;让大家意识到 Scala 虽然提供了用于循环的 while 语句&#xff0c;但大多数情况下&#xff0c;我们有其他更简便的方式能够达到同样的效果。

 

使用牛顿法求解平方根

可假设其平方根为任意一个正数 ( 在这里&#xff0c;我们选定 1 为初始的假设 )&#xff0c;然后比较 x与该数的平方&#xff0c;如果两者足够近似&#xff08;比如两者的差值小于 0.0001&#xff09;&#xff0c;则该正数即为 x的平方根&#xff1b;否则重新调整假设&#xff0c;假设新的平方根为 上次假设与 x/ 上次假设的和的平均数。通过下表可以看到&#xff0c;经过仅仅 4 次迭代&#xff0c;就能求解出相当精确的 2 的平方根。

将上述算法转化为 Scala 程序&#xff0c;首先我们定义这个迭代过程&#xff0c;这也是该算法的核心部分&#xff0c;所幸这一算法非常简单&#xff0c;利用递归&#xff0c;一个 if else表达式就能搞定。后续为两个辅助方法&#xff0c;让我们的程序看起来更加清晰。最后我们选定初始假设为 1&#xff0c;定义出最终的 sqrt方法。

代码如下&#xff1a;

package com.spark.myimport scala.annotation.tailrecobject Hello {def main(args: Array[String]): Unit &#61; {// 测试代码val ret &#61; sqrt(2)println(ret)}// 迭代函数&#xff0c;若解不满足精度&#xff0c;通过递归调用接续迭代def sqrtIter(guess: Double, x: Double): Double &#61;if (isGoodEnough(guess, x))guesselsesqrtIter((guess &#43; x / guess)/2, x)// 判断解是否满足要求def isGoodEnough(guess: Double, x: Double) &#61;abs(guess * guess - x)<0.0001// 辅助函数&#xff0c;求绝对值def abs(x: Double) &#61;if (x <0) -x else x// 目标函数def sqrt(x: Double): Double &#61;sqrtIter(1, x)}

然而这段程序也有一个显而易见的缺陷&#xff0c;作为用户&#xff0c;他们只关心 sqrt函数&#xff0c;但这段程序却将其他一些辅助函数也暴露给了用户&#xff0c;我们在前面提到过&#xff0c;Scala 里可以嵌套定义函数&#xff0c;我们可以将这些辅助函数定义为 sqrt的内部函数&#xff0c;更进一步&#xff0c;由于内部函数可以访问其函数体外部定义的变量&#xff0c;我们可以去掉这些辅助函数中的 x参数。最终的程序如下&#xff1a;

package com.spark.myimport scala.annotation.tailrecobject Hello {def main(args: Array[String]): Unit &#61; {// 测试代码val ret &#61; sqrt(3)println(ret)}// 目标函数&#xff0c;通过将需要用到的辅助函数定义为内部函数&#xff0c;实现细节的隐藏def sqrt(x: Double): Double &#61; {// 迭代函数&#xff0c;若解不满足精度&#xff0c;通过递归调用接续迭代def sqrtIter(guess: Double): Double &#61;if (isGoodEnough(guess))guesselsesqrtIter((guess &#43; x / guess) / 2)// 判断解是否满足要求def isGoodEnough(guess: Double) &#61;abs(guess * guess - x) <0.0001// 辅助函数&#xff0c;求绝对值def abs(x: Double) &#61;if (x <0) -x else xsqrtIter(1)}}

 

如何运行 Scala 程序

作为应用程序执行

作为应用程序执行时&#xff0c;我们需要在一个单例对象中定义入口函数 main&#xff0c;经过编译后就可以执行该应用程序了。

清单 10. HelloWorld.scala

object HelloWorld { def main(args: Array[String]): Unit &#61; { println("Hello World!") } }

Scala 还提供了一个更简便的方式&#xff0c;直接继承另一个对象 App&#xff0c;无需定义 main方法&#xff0c;编译即可运行。

清单 11. HelloScala.scala

object HelloScala extends App { println("Hello Scala!") }

亲测&#xff0c;可用。

 

结束语

本文为大家介绍了 Scala 的基本语法&#xff0c;相比 Java&#xff0c;Scala 的语法更加简洁&#xff0c;比如 Scala 的类型推断可以省略程序中绝大多数的类型声明&#xff0c;短小精悍的匿名函数可以方便的在函数之间传递&#xff0c;还有各种在 Scala 社区约定俗成的习惯&#xff0c;比如省略的分号以及函数体只有一条表达式时的花括号&#xff0c;这一切都帮助程序员写出更简洁&#xff0c;更优雅的程序。限于篇幅&#xff0c;本文只介绍了 Scala 最基本的语法&#xff0c;如果读者想跟进一步学习 Scala&#xff0c;请参考 Scala 的 官方文档及文末所附的参考资源。

掌握了这些基本的语法&#xff0c;我们将在下一篇文章中为大家介绍如何使用 Scala 进行函数式编程&#xff0c;这是 Scala 最让人心动的特性之一&#xff0c;对于习惯了面向对象的程序员来说&#xff0c;学习 Scala 更多的是在学习如何使用 Scala 进行函数式编程。

 

参考资料

学习

  • First Steps to Scala&#xff0c;Scala 入门教程。
  • A Scala Tutorial&#xff0c;一篇面向 Java 程序员的 Scala 教程。
  • Scala By Example&#xff0c;用代码展示 Scala 语言的各种特性。
  • Programming in Scala, First Edition&#xff0c;本书的第一版&#xff0c;可在网上免费阅读。
  • Functional Programming Principles in Scala&#xff0c;Scala 语言的发明者 Martin Odersky 教授在网上的公开课&#xff0c;使用 Scala 语言介绍函数式编程中的一些基本理念。
  • 使用牛顿法求解平方根&#xff0c;Wikipedia 上关于牛顿法的介绍。
  • 面向 Java 开发人员的 Scala 指南系列&#xff0c;DeveloperWorks 上关于 Scala 语言的系列文章。
  • developerWorks Java 技术专区&#xff1a;这里有数百篇关于 Java 编程各个方面的文章。

 

开始学习这篇文章

http://www.ibm.com/developerworks/cn/java/j-lo-funinscala3/index.html

 

即使你是一个刚刚踏入职场的新人&#xff0c;如果在面试时能有意无意地透露出你懂那么一点点函数式编程&#xff0c;也会让你的面试官眼前一亮。

阿兰·图灵&#xff08;Alan Turing&#xff09;和约翰·冯·诺伊曼&#xff08;John von Neumann&#xff09;。阿兰·图灵提出了图灵机的概念&#xff0c;约翰·冯·诺伊曼基于这一理论&#xff0c;设计出了第一台现代计算机。由于图灵以及冯·诺伊曼式计算机的大获成功&#xff0c;历史差点淹没了另外一位同样杰出的科学家和他的理论&#xff0c;那就是阿隆佐·邱奇&#xff08;Alonzo Church&#xff09;和他的λ演算。

阿隆佐·邱奇是阿兰·图灵的老师&#xff0c;上世纪三十年代&#xff0c;他们一起在普林斯顿研究可计算性问题&#xff0c;为了回答这一问题&#xff0c;阿隆佐·邱奇提出了λ演算&#xff0c;其后不久&#xff0c;阿兰·图灵提出了图灵机的概念&#xff0c;尽管形式不同&#xff0c;但后来证明&#xff0c;两个理论在功能上是等价的&#xff0c;条条大路通罗马。

如果不是约翰·麦卡锡&#xff08;John McCarthy&#xff09;&#xff0c;阿隆佐·邱奇的λ演算恐怕还要在历史的故纸堆中再多躺几十年&#xff0c;约翰·麦卡锡是人工智能科学的奠基人之一&#xff0c;他发现了λ演算的珍贵价值&#xff0c;发明了基于λ演算的函数式编程语言&#xff1a;Lisp&#xff0c;由于其强大的表达能力&#xff0c;一推出就受到学术界的热烈欢迎&#xff0c;以至于一段时间内&#xff0c;Lisp 成了人工智能领域的标准编程语言。

很快&#xff0c;λ演算在学术界流行开来&#xff0c;出现了很多函数式编程语言&#xff1a;Scheme 、SML、Ocaml 等&#xff0c;但是在工业界&#xff0c;还是命令式编程语言的天下&#xff0c;Fortran、C、C&#43;&#43;、Java 等。

随着时间的流逝&#xff0c;越来越多的计算机从业人员认识到函数式编程的意义&#xff0c;爱立信公司于上世纪八十年代开发出了 Erlang 语言来解决并发编程的问题&#xff1b;在互联网的发展浪潮中&#xff0c;越来越多的语言也开始支持函数式编程&#xff1a;Javascript、Python、Ruby、Haskell、Scala 等。可以预见&#xff0c;如果过去找一个懂什么是函数式编程的程序员很困难&#xff0c;那么在不久的将来&#xff0c;找一个一点也没听过函数式编程的程序员将更加困难。

 

什么是函数式编程

狭义地说&#xff0c;函数式编程没有可变的变量、循环等这些命令式编程方式中的元素&#xff0c;像数学里的函数一样&#xff0c;对于给定的输入&#xff0c;不管你调用该函数多少次&#xff0c;永远返回同样的结果。而在我们常用的命令式编程方式中&#xff0c;变量用来描述事物的状态&#xff0c;整个程序&#xff0c;不过是根据不断变化的条件来维护这些变量。

广义地说&#xff0c;函数式编程重点在函数&#xff0c;函数是这个世界里的一等公民&#xff0c;函数和其他值一样&#xff0c;可以到处被定义&#xff0c;可以作为参数传入另一个函数&#xff0c;也可以作为函数的返回值&#xff0c;返回给调用者。利用这些特性&#xff0c;可以灵活组合已有函数形成新的函数&#xff0c;可以在更高层次上对问题进行抽象。本文的重点将放在这一部分。

 

函数式编程有什么优点

约翰·巴克斯&#xff08;John Backus&#xff09;为人熟知的两项成就是 FORTRAN 语言和用于描述形式系统的巴克斯范式&#xff0c;因为这两项成就&#xff0c;他获得了 1977 年的图灵奖。

有趣的是他在获奖后&#xff0c;做了一个关于函数式编程的讲演&#xff1a;Can Programming Be Liberated From the von Neumann Style? 1977 Turing Award Lecture。他认为像 FORTRAN 这样的命令式语言不够高级&#xff0c;应该有新的&#xff0c;更高级的语言可以摆脱冯诺依曼模型的限制&#xff0c;于是他又发明了 FP 语言&#xff0c;虽然这个语言未获成功&#xff0c;但是约翰·巴克斯关于函数式编程的论述却得到了越来越多的认可。

 

下面&#xff0c;我们就罗列一些函数式编程的优点。

首先&#xff0c;函数式编程天然有并发的优势。由于工艺限制&#xff0c;摩尔定律已经失效&#xff0c;芯片厂商只能采取多核策略。程序要利用多核运算&#xff0c;必须采取并发&#xff0c;而并发最头疼的问题就是共享数据&#xff0c;狭义的函数式编程没有可变的变量&#xff0c;数据只读不写&#xff0c;并发的问题迎刃而解。这也是前面两篇文章中&#xff0c;一直建议大家在定义变量时&#xff0c;使用 val 而不是 var 的原因。爱立信公司发明的 Erlang 语言就是为解决并发的问题而生&#xff0c;在电信行业取得了不俗的成绩。

其次&#xff0c;函数式编程有迹可寻。由于不依赖外部变量&#xff0c;给定输入函数的返回结果永远不变&#xff0c;对于复杂的程序&#xff0c;我们可以用值替换的方式&#xff08;substitution model&#xff09;化繁为简&#xff0c;轻松得出一段程序的计算结果。为这样的程序写单元测试也很方便&#xff0c;因为不用担心环境的影响。

最后&#xff0c;函数式编程高屋建瓴。写程序最重要的就是抽象&#xff0c;不同风格的编程语言为我们提供了不同的抽象层次&#xff0c;抽象层次越高&#xff0c;表达问题越简洁&#xff0c;越优雅。读者从下面的例子中可以看到&#xff0c;使用函数式编程&#xff0c;有一种高屋建瓴的感觉。

 

抽象&#xff0c;抽象&#xff0c;再抽象&#xff01;

清单 2. 求立方函数

def cube(n: Int) &#61; n * n * n cube(35) cube(68)

 

清单 3. 求立方和

def cube(n: Int) &#61; n * n * n
def sumCube(a: Int, b: Int): Int
&#61; if (a > b) 0 else cube(a) &#43; sumCube(a &#43; 1, b)
sumCube(1, 10)

 

是时候教给他第二项本领了&#xff1a;高阶函数&#xff08;Higher-Order Function&#xff09;&#xff0c;所谓高阶函数&#xff0c;就是操作其他函数的函数。以求和为例&#xff0c;我们可以定义一个新的求和函数&#xff0c;该函数接受另外一个函数作为参数&#xff0c;这个作为参数的函数代表了某种对数据的操作。使用高阶函数后&#xff0c;抽象层次提高&#xff0c;代码变得更简单了。

def cube(n: Int) &#61; n * n * n def id(n: Int) &#61; n def square(n : Int) &#61; n * n def fact(n: Int): Int &#61; if (n &#61;&#61; 0) 1 else n * fact(n - 1) // 高阶函数def sum(f: Int &#61;> Int, a: Int, b: Int): Int &#61; if (a > b) 0 else f(a) &#43; sum(f, a &#43; 1, b) // 使用高阶函数重新定义求和函数def sumCube(a: Int, b: Int): Int &#61; sum(cube, a, b) def sumSquare(a: Int, b: Int): Int &#61; sum(square, a, b) def sumFact(a: Int, b: Int): Int &#61; sum(fact, a, b) def sumInt(a: Int, b: Int): Int &#61; sum(id, a, b) // do itsumCube(1, 10) sumInt(1, 10) sumSquare(1, 10) sumFact(1, 10)

 

对于简单的函数&#xff0c;我们还可以将其转化为匿名函数&#xff0c;让程序变得更简洁一些。在高阶函数中使用匿名函数&#xff0c;这是函数式编程中经常用到的一个技巧&#xff0c;多数情况下&#xff0c;我们关心的是高阶函数&#xff0c;而不是作为参数传入的函数&#xff0c;所以为其单独定义一个函数是没有必要的。

值得称赞的是 Scala 中定义匿名函数的语法很简单&#xff0c;箭头左边是参数列表&#xff0c;右边是函数体&#xff0c;参数的类型是可省略的&#xff0c;Scala 的类型推测系统会推测出参数的类型。使用匿名函数后&#xff0c;我们的代码变得更简洁了&#xff1a;

清单 6. 在高阶函数中使用匿名函数

def fact(n: Int): Int &#61; if (n &#61;&#61; 0) 1 else n * fact(n - 1) // 高阶函数def sum(f: Int &#61;> Int, a: Int, b: Int): Int &#61; if (a > b) 0 else f(a) &#43; sum(f, a &#43; 1, b) // 使用高阶函数重新定义求和函数def sumCube(a: Int, b: Int): Int &#61; sum(x &#61;> x * x * x, a, b) def sumSquare(a: Int, b: Int): Int &#61; sum(x &#61;> x * x, a, b) def sumFact(a: Int, b: Int): Int &#61; sum(fact, a, b) def sumInt(a: Int, b: Int): Int &#61; sum(x &#61;> x, a, b) // 有了这些函数&#xff0c;小龙做起作业轻松多了sumCube(1, 10) sumInt(1, 10) sumSquare(1, 10) sumFact(1, 10)

 

柯里化

上面使用匿名函数后的高阶函数还有什么地方值得改进呢&#xff1f;希望大家还会想起那句话&#xff1a;Don ’ t Repeat Yourself &#xff01;求和函数的两个上下限参数 a,b被重复得传来传去。我们试着重新定义 sum函数&#xff0c;让它接受一个函数作为参数&#xff0c;同时返回另外一个函数。看到没&#xff1f;使用新的 sum函数&#xff0c;我们再定义各种求和函数时&#xff0c;完全不需要这两个上下限参数了&#xff0c;我们的程序又一次得到了简化。

清单 7. 返回函数的高阶函数

def fact(n: Int): Int &#61; if (n &#61;&#61; 0) 1 else n * fact(n - 1) // 高阶函数def sum(f: Int &#61;> Int): (Int, Int) &#61;> Int &#61; { def sumF(a: Int, b: Int): Int &#61; if (a > b) 0 else f(a) &#43; sumF(a &#43; 1, b) sumF } // 使用高阶函数重新定义求和函数def sumCube: Int &#61; sum(x &#61;> x * x * x) def sumSquare: Int &#61; sum(x &#61;> x * x) def sumFact: Int &#61; sum(fact) def sumInt: Int &#61; sum(x &#61;> x) // 这些函数使用起来还和原来一样 ! sumCube(1, 10) sumInt(1, 10) sumSquare(1, 10) sumFact(1, 10)

能不能再简单一点呢&#xff1f;既然 sum返回的是一个函数&#xff0c;我们应该可以直接使用这个函数&#xff0c;似乎没有必要再定义各种求和函数了。

清单 8. 直接调用高阶函数

def fact(n: Int): Int &#61; if (n &#61;&#61; 0) 1 else n * fact(n - 1) // 高阶函数def sum(f: Int &#61;> Int): (Int, Int) &#61;> Int &#61; { def sumF(a: Int, b: Int): Int &#61; if (a > b) 0 else f(a) &#43; sumF(a &#43; 1, b) sumF } // 这些函数没有必要了//def sumCube: Int &#61; sum(x &#61;> x * x * x) //def sumSquare: Int &#61; sum(x &#61;> x * x) //def sumFact: Int &#61; sum(fact) //def sumInt: Int &#61; sum(x &#61;> x) // 直接调用高阶函数 ! sum(x &#61;> x * x * x) (1, 10) //&#61;> sumCube(1, 10) sum(x &#61;> x) (1, 10) //&#61;> sumInt(1, 10) sum(x &#61;> x * x) (1, 10) //&#61;> sumSquare(1, 10) sum(fact) (1, 10) //&#61;> sumFact(1, 10)

 

我自己在Intellij里面也写了一下这一块的功能&#xff08;我用的是first-spark-demo这个project&#xff09;&#xff1a;

package com.spark.myimport scala.annotation.tailrecobject Hello{def main(args: Array[String]): Unit &#61; {val ret &#61; sum(x&#61;> x*x)(1, 2)println(ret)}def sum(f: Int &#61;> Int): (Int, Int)&#61;> Int &#61; {def sumF(a: Int, b: Int): Int &#61;if (a > b) 0 else f(a) &#43; sumF(a&#43;1, b)sumF}}

 

这种返回函数的高阶函数极为有用&#xff0c;因此 Scala 为其提供了语法糖&#xff0c;上面的 sum函数可以简写为&#xff1a;

清单 9. 高阶函数的语法糖

我在Intellij里面实际写了一下&#xff1a;

package com.spark.myimport scala.annotation.tailrecobject Hello{def main(args: Array[String]): Unit &#61; {val ret &#61; sum(x&#61;> x*x)(1, 2)println(ret)}def sum(f: Int &#61;> Int)(a: Int, b: Int): Int &#61;if (a > b) 0 else f(a) &#43; sum(f)(a&#43;1, b)}

注意&#xff0c;以上语法糖和原来的写法&#xff0c;还是有很多区别的&#xff1a;

首先sum()()两个括号直接没有冒号

另外 ()(): Int 中间是冒号&#xff0c;不是&#61;>

然后再下面的表达式中&#xff0c;用到了上面第二个括号里面定义的变量a和b

 

我们把原来的 sum函数转化成这样的形式&#xff0c;好处在哪里&#xff1f;答案是我们获得了更多的可能性&#xff0c;比如刚开始求和的上下限还没确定&#xff0c;我们可以在程序中把一个函数传给 sum&#xff0c;sum(fact)完全是一个合法的表达式&#xff0c;待后续上下限确定下来时&#xff0c;再把另外两个参数传进来。

对于 sum 函数&#xff0c;我们还可以更进一步&#xff0c;把 a&#xff0c;b 参数再转化一下&#xff0c;这样 sum 函数就变成了这样一个函数&#xff1a;它每次只能接收一个参数&#xff0c;然后返回另一个接收一个参数的函数&#xff0c;调用后&#xff0c;又返回一个只接收一个参数的函数。

这就是传说中的柯里化&#xff0c;多么完美的形式&#xff01;在现实世界中&#xff0c;的确有这样一门函数式编程语言&#xff0c;那就是 Haskell&#xff0c;在 Haskell 中&#xff0c;所有的函数都是柯里化的&#xff0c;即所有的函数只接收一个参数&#xff01;

注&#xff1a;柯里化(currying)&#xff1a;又称部分求值&#xff08;Partial Evaluation&#xff09;&#xff0c;是把接受多个参数的函数变换成接受一个单一参数&#xff08;最初函数的第一个参数&#xff09;的函数&#xff0c;并且返回接受余下的参数而且返回结果的新函数的技术。

清单 10. 柯里化

我在Intellij里面写的&#xff1a;

package com.spark.myimport scala.annotation.tailrecobject Hello{def main(args: Array[String]): Unit &#61; {val ret &#61; sum(x&#61;> x*x)(1)(2)println(ret)}def sum(f: Int &#61;> Int)(a: Int)(b: Int): Int &#61;if (a > b) 0 else f(a) &#43; sum(f)(a&#43;1)(b)

}

可以看到&#xff0c;非常巧妙&#xff01;相当于接收第一个参数&#xff0c;返回接受后续参数的函数&#xff0c;直到参数个数足够为止&#xff0c;得出最后的结果。

 

结束语

在 Scala 类库中&#xff0c;使用函数式编程的例子比比皆是&#xff0c;特别是对于列表的操作&#xff0c;将高阶函数的优势展示得淋漓尽致&#xff0c;限于篇幅&#xff0c;不能在本文中为大家作以介绍&#xff0c;作者将在后面的系列文章中&#xff0c;以 Scala 中的列表为例&#xff0c;详细介绍高阶函数在实战中的应用。&#xff08;还没找到后面的文章在哪里&#xff09;

参考资料

学习

  • Programming in Scala, First Edition&#xff0c;学习 Scala 的参考书籍&#xff0c;可在网上免费阅读。
  • Functional Programming Principles in Scala&#xff0c;Scala 语言的发明者 Martin Odersky 教授在网上的公开课&#xff0c;使用 Scala 语言介绍函数式编程中的一些基本理念。
  • Functional Programming For The Rest of Us&#xff0c;一篇非常有趣的文章&#xff0c;作者以幽默的语言讲解了函数式编程的历史&#xff0c;并使用 Java 讲解了函数式编程中的相关概念。
  • Learning Scala in small bites&#xff0c;提供了很多例子展示 Scala 的语法。
  • Higher-order list operations&#xff0c;使用 Racket 和 Haskell&#xff0c;以函数式编程实现常用的列表操作。
  • Functional Programming in 5 Minutes&#xff0c;使用 Javascript 讲解了柯里化。
  • Learn from Haskell - Functional, Reusable Javascript&#xff0c;借鉴 Haskell&#xff0c;写出函数式编程风格的 Javascript 代码。
  • Functional Programming&#xff0c;使用 Javascript 讲解函数式编程。
  • Higher order functions&#xff0c;使用 Haskell 讲解高阶函数。
  • 有趣的 Scala 语言 : 使用递归的方式去思考&#xff0c;该系列文章的第一篇&#xff0c;介绍了递归在编程中的应用&#xff0c;并提供了大量使用递归的方式解决问题的例子。
  • 有趣的 Scala 语言 : 简洁的 Scala 语法&#xff0c;该系列文章的第二篇&#xff0c;介绍了 Scala 的基本语法&#xff0c;帮助 Scala 新手上路。
  • developerWorks Java 技术专区&#xff1a;这里有数百篇关于 Java 编程各个方面的文章。

 

后续再看看JS里面关于函数编程的部分吧。关键是把函数编程的思想领悟了。

 

转:https://www.cnblogs.com/charlesblc/p/6110484.html



推荐阅读
  • Java自带的观察者模式及实现方法详解
    本文介绍了Java自带的观察者模式,包括Observer和Observable对象的定义和使用方法。通过添加观察者和设置内部标志位,当被观察者中的事件发生变化时,通知观察者对象并执行相应的操作。实现观察者模式非常简单,只需继承Observable类和实现Observer接口即可。详情请参考Java官方api文档。 ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
  • 数组的排序:数组本身有Arrays类中的sort()方法,这里写几种常见的排序方法。(1)冒泡排序法publicstaticvoidmain(String[]args ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • C++中的三角函数计算及其应用
    本文介绍了C++中的三角函数的计算方法和应用,包括计算余弦、正弦、正切值以及反三角函数求对应的弧度制角度的示例代码。代码中使用了C++的数学库和命名空间,通过赋值和输出语句实现了三角函数的计算和结果显示。通过学习本文,读者可以了解到C++中三角函数的基本用法和应用场景。 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
author-avatar
敏佳的晴__丶天
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有