Scala学习笔记(六):函数


1.定义函数最通用的方式是作为某个对象的成员,这种函数被称为方法

2.本地函数或者局部函数 - 把函数定义在别的函数之内

局部函数的作用域和局部变量作用域一样

局部函数访问包含该函数的参数是一种常见的嵌套函数的用法

import scala.io.Source
object LongLines {
  def processFile(filename: String, width: Int) {
    def processLine(line:String){
     if(line.length > width)
       println(filename + ":" +line.trim)
   }
    val source= Source.fromFile(filename)
    for (line <- source.getLines())
      processLine(line)
   }
}

3.命名参数 - 指明参数名称

通常情况下,调用函数时传入的参数与函数定义时的参数列表一一对应。

scala> def  speed(distance: Float, time: Float): Float = distance/time
speed: (distance: Float, time: Float)Float
scala> speed(100,10)
res0: Float = 10.0

使用命名参数允许用任意顺序传入参数

scala> speed(time=10, distance=100)
res1: Float = 10.0

4.缺省参数

Scala 在定义函数时,允许指定参数的缺省值,从而允许在调用函数时可以不指明该参数,进而使用其缺省值。

缺省参数通常配合命名参数使用

scala> def printTime(out:java.io.PrintStream = Console.out, divisor:Int =1 ) =
     | out.println("time = " + System.currentTimeMillis()/divisor)
printTime: (out: java.io.PrintStream, divisor: Int)Unit
scala> printTime()
time = 1383220409463
scala> printTime(divisor=1000)
time = 1383220422

5.变长参数

Scala 在定义函数时允许指定最后一个参数可以重复(变长参数),从而允许函数调用者使用变长参数列表来调用该函数

Scala 中使用“*”来指明参数为重复参数

例如:

def echo(args:String*)=
  for(arg<-args) println(arg)
echo("here")
echo("here","there")

变长参数的类型为数组

上例的 String * 类型实际为 Array[String], 然而,如果直接传入一个数组类型的参数给这个函数,编译器会报错

为了避免这种情况,可以在变量后面添加 _*

这个符号告诉 Scala 编译器在传递参数时逐个传入数组的每个元素

6.函数参数

def filesMatching( query: String, matcher: (String, String) => Boolean) = {
    for(file <- filesHere; if matcher(file.getName, query))
      yield file
}

此处 filesMatching 函数的第二个参数 matcher 的类型为函数,可以匹配任意【以两个 String 类型为参数,且返回值类型为 Boolean】的函数

于是上述函数可以作为common函数,示例:

def filesEnding(query:String) =
   filesMatching(query, _.endsWith(_))
def filesContaining(query:String)=
   filesMatching(query, _.contains(_))

扩展:

利用闭包,上述代码还可以进一步简化

object FileMatcher {
  private def filesHere = (new java.io.File(".")).listFiles

  def filesMatching(matcher: (String) => Boolean) = {
    for (file <- filesHere; if matcher(file.getName))
      yield file
  }

  def filesEnding(query: String) =
    filesMatching(_.endsWith(query))

  def filesContaining(query: String) =
    filesMatching(_.contains(query))

  def filesRegex(query: String) =
    filesMatching(_.matches(query))
}

7.尾递归

函数最后一行如果是调用其本身,把这种递归叫做尾递归

Scala 编译器可以检测到尾递归,会做些优化,进而使用循环来代替

可以添加 scalac 编译参数 -g:notailcalls 来取消这个优化,此后,如果抛出异常,尾递归会显示多层调用栈

8.函数字面量

把函数写成匿名的字面量

(x :Int ) => x +1

上述示例中 => 符号表示这个函数将符号左边的东西转换成符号右边的东西

可以把它当成一个值传递到其它函数或是赋值给其它变量,示例:

var increase = (x :Int ) => x +1
increase(10)

且很多 Scala 的库允许用函数作为参数,比如 foreach 方法或者 filter 方法,如下:

args.foreach((x:Int) => println(x))
val numbers = List ( -1, -12, 5, 0, -5, 1)
numbers.filter(x => x > 0)

注意:函数字面量 (x:Int) => x + 1 在 Scala 内部表示为带有一个参数的类 Function1 的一个对象

FunctionN 代表带有N个参数的函数,Function0 代表不含参数的函数

如果函数定义需要多条语句,可以使用{},比如:

var increase = (x :Int ) => {
  println("We")
  println("are")
  println("here")
  x + 1
}

函数字面量的短格式

Scala 允许使用“占位符”即下划线(”_”)来替代一个或多个参数,如果这个参数在函数定义中只出现一次,Scala 编译器可以推断出参数类型

val numbers = List ( -11, -10, - 5, 0, 5, 10)
numbers.filter(_ > 0)

下述代码需要指明 _ 的参数类型

val f=(_:Int)+(_:Int)
f(1,2)

9.部分应用函数

在 Scala 中,当调用函数,并传入所需参数,就是把函数“应用”到参数。

部分应用函数指的是:在调用函数时,不指定函数所需的所有参数,这样就创建了一个新的函数,这个新的函数就称为原始函数的部分应用函数

def sum = (_:Int) + (_ :Int) + (_ :Int)

针对上述代码,固定第一个和第三个参数

scala> val b = sum(1, _ :Int, 3)
b: Int => Int = 
scala> b(2)
res1: Int = 6

变量 b 的类型为 Function1(带一个参数的函数),它是由 sum 应用了第一个和第三个参数构成的。

调用b(2),实际上是调用 sum(1,2,3)。

实际上,在某些情况下可以使用“_”来代替整个参数列表

numbers.foreach(println _)

在 Scala 中,如果定义一个部分应用函数并且能省去所有参数,则下划线“_”也可以省去

numbers.foreach(println)

10.“柯里化”

使用“柯里化”技术可以将原来使用一个参数列表的函数转换为使用多个参数列表的函数

scala> def plainOldSum(x:Int,y:Int) = x + y
plainOldSum: (x: Int, y: Int)Int
scala> plainOldSum(1,2)
res0: Int = 3

转换:将一个参数列表变成两个参数列表,每个列表含一个参数

scala> def curriedSum(x:Int)(y:Int) = x + y
curriedSum: (x: Int)(y: Int)Int

此函数接受两个参数列表,调用如下:

scala> curriedSum (1)(2)
res0: Int = 3

当调用 curriedSum (1)(2)时,实际上是依次调用两个普通函数,第一次调用使用参数 x 并返回一个函数类型的值,第二次使用参数 y 调用返回的这个函数类型的值

scala> val onePlus = curriedSum(1)_
onePlus: Int => Int = 

下划线(“_”)为第二个参数列表的占位符

scala> onePlus(2)
res2: Int = 3

11.

在 Scala 中,如果调用的函数只有一个参数,可以用{}来替代()

scala> println ("Hello,World")
Hello,World
scala> println { "Hello,world" }
Hello,world

如果函数使用了两个参数,则不能用{}来替代()

def withPrintWriter (file: File, op: PrintWriter => Unit) {
  val writer=new PrintWriter(file)
  try{
    op(writer)
  }finally{
    writer.close()
  }
}

但如果使用“柯里化”重新定义函数,则有这种可能

import scala.io._
import java.io._
def withPrintWriter (file: File)( op: PrintWriter => Unit) {
  val writer=new PrintWriter(file)
  try{
    op(writer)
  }finally{
    writer.close()
  }
}

调用:

val file = new File("date.txt")
withPrintWriter(file){
  writer => writer.println(new java.util.Date)
}

上面这段代码的写法类似于 Scala中的 if、while 等内置语法

if(x > y) {
  x += 1
}

12.针对重复操作的优化

scala> def twice (op:Double => Double, x:Double) =op(op(x))
twice: (op: Double => Double, x: Double)Double
scala> twice(_ + 1, 5)
res0: Double = 7.0

13.传名参数

 

相关