Scala要点 - 1.函数与求值

[toc]

Martin Odersky - Working Hard to Keep It Simple

1.1 编程范式

主要编程范式:

  • 命令式编程
    • 定义可变变量
    • 使用赋值
    • 使用控制结构,如 if-else-then, loops, break, continue, return
  • 函数式编程
    • 严格意义上,函数式编程意味着没有可变变量,赋值,循环和其他命令式控制结构
    • 广泛来讲,函数式编程意味着关注于函数
    • 特别地,函数可以是生成,使用和组合的值
    • 函数是一等公民
      • 可定义于任何地方,包括其它函数内部
      • 可作为函数参数和返回值
      • 一系列操作组合成函数
  • 逻辑编程

与之相对的是:

  • 面向对象编程
    • 非严格意义上的编程范式,是上述几种范式的一个交叉产物

1.2 编程元素

算术表达式求值过程
(2 * pi) * radius
(2 * 3.14159) * radius
6.28318 * radius
6.28318 * 10
62.8318

带有参数的定义

scala> def square(x: Double) = x * x
square: (Double)Double

scala> square(2)
4.0

scala> square(5 + 4)
81.0

scala> def sumOfSquares(x: Double, y: Double) = square(x) + square(y)
sumOfSquares: (Double, Double)Double

scala> def power(x: Double, y: Int): Double = ...

代换模型

所有求值过程都会将一个表达式化简成一个值

求值策略

scala通常使用 call-by-value

call-by-value (CBV)

优势:所有函数参数只求值一次

sumOfSquares(3, 2+2)
sumOfSquares(3, 4)
square(3) + square(4)
9 + square(4)
9 + 16
25  
call-by-name (CBN)

优势:函数体未用到的参数不会被求值

sumOfSquares(3, 2+2)
square(3) + square(2+2)
3 * 3 + square(2+2)
9 + square(2+2)
9 + (2+2) * (2+2)
9 + 4 * 4
9 + 16
25

1.3 求值策略和终止

def loop: Int = loop

def first(x: Int, y: Int) = x

使用CBN first(1, loop) 结果为1
使用CBVfirst(1, loop) 死循环

scala通常使用call-by-value,要使用call-by-name,在参数中使用=>

def constOne(x: Int, y: => Int) = 1

第一个参数为call-by-value,第二个参数为call-by-name

constOne(1 + 2, loop)
constOne(3, loop)
1
constOne(loop, 1 + 2)
...无限循环

1.4 条件语句和值定义

if-else

def abs(x: Int) if (x < 0) -x else x

Boolean

expression result
!true false
!false true
true && e e
false && e false
true || e true
false || e e

值定义 (def vs val)

def属于by-name的形式,每次使用时对右侧求值
val属于by-value的形式,定义时对右侧求值,变量名指向值

val x = 2
val y = square(x)

y 指向 4,而不是 square(2)

def loop: Boolean = loop

def x = loop 可以定义
val x = loop 导致死循环

不使用 && 和 || 定义 and(x, y) 和 or(x, y)
def and(x: Boolean, y: => Boolean) = if(x) y else false

def or(x: Boolean, y: => Boolean) = if(!x) false else y

第二个参数不使用=>的话,在调用and(false, loop)时会死循环

def or(x: Boolean, y: => Boolean) = if(x) true else y
def or(x: Boolean, y: => Boolean) = if(!x) y else true

1.5 用牛顿方法求平方根

计算 sqrt(x)

  • 从一个初始的估计值y开始(正数即可,这里选择 y = 1)
  • 重复对 y 和 \frac{x}{y} 取平均值来提升精确度,平均值作为下一轮估计值

sqrt(2)

估计 平均值(新的估计)
1 2 / 1 = 2 1.5
1.5 2 / 1.5 = 1.333 1.4167
1.4167 2 / 1.4167 = 1.4118 1.4142
1.4142 ... ...
object session {

  def abs(x: Double) = if (x < 0) -x else x

  def sqrtIter(guess: Double, x: Double): Double =
    if (isGoodEnough(guess, x)) guess
    else sqrtIter(improve(guess, x), x)

  def isGoodEnough(guess: Double, x: Double) =
    abs(guess * guess - x) / x < 0.001

  def improve(guess: Double, x: Double) =
    (guess + x / guess) / 2

  def sqrt(x: Double) = sqrtIter(1.0, x)

  sqrt(2)
  sqrt(4)
  sqrt(1e-6)
  sqrt(1e30)
}

有问题的实现:

  def isGoodEnough(guess: Double, x: Double) =
    abs(guess * guess - x) < 0.001

小数字不精确,大数字不会终止。
对于小数字,x可能会小于阈值(这里的0.001)
对于大数字,???

1.6 词法作用域和语句块

嵌套函数

def sqrt(x: Double) = {
  def sqrtIter(guess: Double, x: Double): Double =
    if (isGoodEnough(guess, x)) guess
    else sqrtIter(improve(guess, x), x)

  def improve(guess: Double, x: Double) =
    (guess + x / guess) / 2

  def isGoodEnough(guess: Double, x: Double) =
    abs(square(guess) - x) < 0.001

  sqrtIter(1.0, x)
}

语句块

定义方式:

// 它的值由最后一个表达式确定,可定义在任何地方
{ ... }

可见性:块内-外部不可见,外部-块内可见,相同变量名,内部覆盖外部
因此,内部函数中的参数x可以省略:

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)
  sqrt(1e-6)
  sqrt(1e60)
}

分号

// Scala中分号是可选的,通常会省略。
val y = 2;
// 如果多个语句在同一行,用分号隔开
val y = x + 1; y * y
// 长表达式分行放置
someLongExpression
+ someOtherExpression

// 上面表达式会被编译器分解成两行
someLongExpression;
+ someOtherExpression

// 可将长语句括号,不会被分解成多行语句
(someLongExpression
+ someOtherExpression)

// 或者将操作符放在第一行,告诉编译器表达式尚未结束
someLongExpression +
someOtherExpression

1.7 尾递归 (tail recursion)

计算最大公约数(欧几里得算法)

def gcd(a: Int, b: Int): Int =
  if (b == 0) a else gcd(b, a % b)

gcd(14, 21)执行过程:

if (21 == 0) 14 else gcd(21, 14 % 21)
if (false) 14 else gcd(21, 14 % 21)
gcd(21, 14 % 21)
gcd(21, 14)
if (14 == 0) 21 else gcd(14, 21 % 14)
...
gcd(14, 7)
...
gcd(7, 0)
if (0 == 0) 7 else gcd(0, 7 % 0)
7

阶乘

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

factorial(4)执行过程:

if (4 == 0) 1 else 4 * factorial(4 - 1)
...
4 * factorial(3)
4 * (3 * factorial(2))
4 * (3 * (2 * factorial(1)))
4 * (3 * (2 * (1 * factorial(0))))
4 * (3 * (2 * (1 * 1)))
120

Scala中的尾递归

如果一个函数将调用自身作为最后一个操作,则这个函数的栈帧 (stack frame) 可以重用。这被称为尾递归。

尾递归执行使用常数级的栈空间。

函数的最后一个动作包括调用一个函数(可能为自身),一个栈帧就足够两个函数使用。这被称为尾调用 (tail-calls)

gcd函数中最后一个操作为调用自身,是尾递归
factorial函数最后一个操作为 n * factorial(n - 1),非尾递归

可以使用@tailrec来指定一个函数为尾递归

@tailrec
def gcd(a: Int, b: Int): Int = ...  // 如果实现方式非尾递归会报错
尾递归版 factorial
def factorial(n: Int): Int = {
  def loop(acc: Int, n: Int): Int = 
    if (n == 0) acc
    else loop(acc * n, n - 1)
  loop(1, n)
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容