Step into Scala - 05 - 块与函数

目录

摘要

块与赋值,创建函数,函数调用与括号,默认参数,带名参数,变参与 List,过程,匿名函数,内嵌函数,尾递归,参数传递的两种方式,部分应用,柯理化

块与函数

  • 块是用 {} 包围的代码
  • Java 中块用于区分作用域和语句序列
  • Scala 中块则是一组表达式的集合,块中的最后一个表达式的值就是块的值
  • 赋值语句本身没有值,即为 Unit(相当于 Java 中的 Void)

Java 中

int y = 0;
int x = y = 10;

以上语句执行完后 x = 10y = 10

Scala 中

var y = 0
val x = y = 10

上述语句中 y = 10 的结果是 Unit,然后再将其赋给 x,这与 Java 有很大差别

函数

定义
  • 函数不同于方法,不是对对象进行操作。
  • 定义一个函数需要函数名,函数参数和函数体。
  • 所有函数参数必须指明类型
  • 除了递归函数,都可以不指明返回值类型。
  • 函数是一等公民,和其它类型一样,可以作为参数,也可以作为返回值。
创建函数

格式

def functionName(argumentName: arguemntType): returnType = {function body}

以 def 进行命名,函数只有一句时可以省略”{}”,参数名写在参数类型之前。

def addOne(m: Int): Int = m + 1
def addTwo(m: Int): Int = {
  m + 2
}
调用函数

当函数定义时没有加上 () 时,调用时也不能加上 ()

def five: Int = {
  val x = 2 + 3
  val y = 3 + 2
  x + y
}
//println(five()) error
println(five)

如果函数定义时加上了 (),那么如果没有参数时调用可加可不加 ()

//无需参数
def four() = 1 + 3
println(four())
println(four)

//完整调用
println(addOne(3))
默认参数和带名参数

调用某些方法时可以不指定所有参数,而使用默认值

def decorate(str: String, left: String = "[", right: String = "]") = left + str + right

可以在调用函数时,指定参数的名称

decorate("abc", right = ">")
可变长度参数
  • 可变长参数使用 * 修饰
  • 使用变长参数时只能一个个传值,不能直接使用外部的 List,除非使用 : _* 表示将 List 中的每个元素当做参数处理
def capitalizeAll(args: String*) = {
  args.map { arg =>
    arg.capitalize
  }
}
println(capitalizeAll("abc", "def"))  //ArrayBuffer(Abc, Def)
println(capitalizeAll("abc"))         //ArrayBuffer(Abc)
println(capitalizeAll())              //List()

def list = List("abc", "zxc")
println(capitalizeAll(list: _*)) //List(Abc, Zxc)
过程

当返回类型为 Unit 时,可以不加等号,表示过程,即没有返回值

def box(s: String) {
  println(s)
}
匿名函数
val add = (x: Int) => x + 1
println(add(3))

为避免歧义,最好将匿名函数定义为常量,而非变量

匿名函数的本质实际是以下方法的语法糖

val add = { def f(x: Int) = x + 1; f _ }

var triple = (x: Double) => 3 * x
println(triple(2)) //6.0

//基于参数类型推断的简写形式
triple = ((x) => 3 * x)
triple = (x => 3 * x)   //适用于只有一个参数
triple = (3 * _)        //适用于只有一个参数
内嵌函数 Nested Functions

函数中嵌套函数,用于避免将计算中间值的函数暴露出来

def closeTo(src: Double, target: Int): Double = {
  def isGoodEnough(guess: Double): Boolean = {
    target - guess < 0.1
  }
  def add(guess: Double): Double = {
    guess + 0.01
  }
  def improve(guess: Double): Double = {
    if (isGoodEnough(guess)) guess
    else improve(add(guess))
  }
  improve(src)
}
println(closeTo(1.2, 5)) //4.900999999999993
尾递归
传统递归
def factorial(n: Int): Int = {
  if (n <= 1) 1
  else n * factorial(n - 1)
  //n * factorial(n - 1) 为递归
}
factorial(5)

以上传统递归时每一步都需要保持上次之前的 n 的值,开辟新的函数栈。当 n 很大时,函数栈将很快被耗尽。

尾递归

尾递归是递归的一种,特点在于会在函数的最后仅仅调用本身,无需额外记住其它数值。所以尾递效率和循环相近,是函数式编程的常见写法。

def factorialTail(n: Int): Int = {
  @tailrec
  def loop(acc: Int, n: Int): Int =
    if (n == 0) acc else loop(n * acc, n - 1)
    //loop(n * acc, n - 1) 为尾递归

  loop(1, n)
}
factorialTail(5)
参数传递
按值传递

函数默认与 Java 一样都是按值传递

def square(x: Double) = x * x
def sum(x: Double, y: Double) = square(x) + square(y)
sum(3, 2 + 2)

调用顺序为

sum(3, 2 + 2)
 sum(3, 4)
   square(3) + square(4)
     3 * 3 + square(4)
       9 + square(4)
按名称传递

Scala 中也可以按名称传递,规则为在参数后加上 “=>” 符号

def square(x: Double) = x * x
def sum2(x: Double, y: => Double) = square(x) + square(y)

调用顺序为

sum(3, 2 + 2)
  square(3) + square(2 + 2)
    3 * 3 + square(2 + 2)
      9 + square(2 + 2)
        9 + (2 + 2) * (2 + 2)
区别

call-by-value 可以避免重复计算的次数,通常更高效
call-by-name 直到使用时才会进行计算
call-by-value 是函数的默认调用方式,使用 call-by-name 需要在指定的参数前后加上 => 符号

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

constOne(1, loop)   //返回1
constOne(loop, 1)   //无限循环

以上操作中参数 “x” 是 call-by-value,参数 “y” 是 call-by-name
从这里可以看到 call-by-name 在某些情况下可以避免死循环

部分应用与柯里化
Partial Application

指调用时不指定所有参数,而使用特殊符号 _。表示部分应用从而得到一个新的函数。
Scala 中的 _ 表示不同上下文中的不同事物。

def adder(m: Int, n: Int): Int = m + n
def add2 = adder(2, _: Int)
println(add2(3)) //5
Currying
def multi(m: Int, n: Int): Int = m * (n + 2)
val multi2 = (multi _).curried
var multi3 = multi2(5)
println(multi3(9)) //55

def multiply(m: Int)(n: Int): Int = m * n
val times2 = multiply(2) _
println(times2(3)) //6

def connect(m: String, n: String, o: String) = m + n + o
val conn2 = (connect _).curried
println(conn2("abc")) //<function1>
def connect2(m: String)(n: String)(o: String) = m + n + o
val connect21 = connect2("abc") _
println(connect21("def")) //<function1>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值