Scala 中的函数式编程基础(一)
主要来自 Scala 语言发明人 Martin Odersky 教授的 Coursera 课程 《Functional Programming Principles in Scala》。
很久以前写过一个非常简单的 python lambda 函数博客,里头有 filter,map,reduce等,是python中很有意思的部分,可以先看看。
另外酷壳网陈皓写了一篇介绍函数式编程的博客,言简意赅,对理解函数式编程很有帮助,非常值得一看。
Scala 本意是可伸展。它的设计哲学是:允许用户通过定义感觉像原生语言支持一样的易用库去在他们需要的方向上改进和发展语言——Scala allows users to grow and adapt the language in the directions they need by defining easy-to-use libraries that feel like native language support.。Scala 运行在 Java 平台上,可以与所有的 Java 库无缝交互。
1. Function evaluations
Scala 是纯粹的面向对象式的编程,所有的 value 都是一个对象。
Scala 是函数式的编程,它把每一个函数看做一个 value,函数即对象。
1.1 函数的副作用
纯函数(Pure Function)是这样一种函数——输入输出数据流全是显式(Explicit)的。
显式(Explicit)的意思是,函数与外界交换数据只有一个唯一渠道——参数和返回值;函数从函数外部接受的所有输入信息都通过参数传递到该函数内部;函数输出到函数外部的所有信息都通过返回值传递到该函数外部。
隐式(Implicit)的意思是,函数通过参数和返回值以外的渠道,和外界进行数据交换。比如,读取全局变量,修改全局变量,都叫作以隐式的方式和外界进行数据交换;比如,利用I/O API(输入输出系统函数库)读取配置文件,或者输出到文件,打印到屏幕,都叫做隐式的方式和外界进行数据交换。
Pure Function的好处主要有几点:
- 无状态,Stateless。线程安全。不需要线程互斥或同步。
- Pure Function相互调用组装起来的函数,还是Pure Function。
- 应用程序或者运行环境(Runtime)可以对Pure Function的运算结果进行缓存,运算加快速度。
在函数式编程语言里面,函数是一等公民,这意味着:
- 像其他 value,可以在任何地方定义函数,包括函数内部
- 像其他 value,函数可以作为其他函数的输入参数或者返回值
- 像其他 value,函数也有自己的操作符进行组合等操作
1.2 参数调用的区别
scala 函数定义格式如下:
- call by value
遇到表达式就计算,scala 函数参数默认进入函数内部之前计算出 value,再传进内部,关键字 val 也是立即计算模式 - call by name
惰性求值,这是函数式编程里面一个非常重要的概念。要用到的时候才计算,可以用def fun(x:Int, y: => Int) = ...
中的=>
显式调用。关键字 def 也是这种模式
举个例子:
def loop: Int = loop // 定义一个死循环,def 可以,val 不行
def constOne(x: Int, y: => Int) = 1 // 第一个参数x: call by value, 第二个参数y: call by name
constOne(1+2, loop) // loop一直没用,所以没有计算,输出1
constOne(loop, 1+2) // 遇到loop立即计算,陷入死循环
1.3 牛顿法求平方根
- Scala 中递归函数需要写返回值类型,非递归不一定要写。
- 函数定义可以放在函数内部,防止命名污染。
牛顿法的基本思路是递归,首先猜测为 1.0,如果误差过大,则猜测值改为 guess 与 x / guess 的平均值。
object session {
def abs(x: Double) = if (x>=0) x else (-x)
def sqrt(x: Double) = {
def sqrtIter(guess: Double): Double =
if (isGoodEnough(guess)) guess
else sqrtIter(improve(guess))
def isGoodEnough(guess: Double) =
abs(guess * guess - x) / x < 0.001
def improve(guess: Double) =
(guess + x / guess) / 2
sqrtIter(1.0)
}
sqrt(2.0)
}
1.4 尾递归
这篇博客推荐看看——递归与尾递归总结。
递归相对常用的算法如普通循环等,运行效率较低。在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储,因此递归次数过多容易造成栈溢出。
尾递归对应 imperative program(如c++)中的循环。 函数调用出现在调用者函数的尾部, 因为是尾部, 所以根本没有必要去保存任何局部变量(需要保存的变量可以放在函数参数列表里)。它的栈是常数,可复用,与循环一样高效。
递归:
// 阶乘函数
def factorial(n: Int): Int =
if (n == 0) 1 else n * factotial(n - 1)
尾递归:
// 阶乘函数
def factorial(n: Int) = {
def loop(acc: Int, n: Int): Int=
if (n == 0) acc
else loop(acc * n, n - 1)
loop(1, n)
}