SCALA 函数用法浅析

来源:互联网 发布:php面向对象编程 pdf 编辑:程序博客网 时间:2024/05/07 17:05

        对于函数式编程来说,函数都是作为第一类值的,你可以将函数看作一种普通变量类型进行赋值,作为参数传递,作为返回值等。SCALA 也不例外,除此之外,SCALA 还提供了一些精妙的语法来简化函数的使用,这里根据个人理解对SCALA 中函数的使用进行一个总结。

  • 函数定义方式
        SCALA 中函数的定义是以 def 关键字打头的,如果函数有返回值则函数体前面必须加上等号,否则可以不写等号。例如:
    def myFunc(x: A):B = {...}

对于不带参数的函数,还可以这样定义:
    def myFunc:B = {...}

当然你非要加个小括号写也是可以的,如:
    def myFunc():B = {...}

这两种方式定义的不带参数函数在使用上是有一定区别的,前者只能不带小括号调用(根据统一访问原则),后者可以带小括号也可以不带。
  • 函数和方法

        函数和方法的区别在于方法隶属于某个类,只能通过这个类或者其实例进行调用;而函数在任何可以定义表达式的地方都可以定义,在任何可见该函数的范围内均可使用。

  • 函数类 FunctionN
        SCALA 中定义一些带参数的函数类,如:
    Function0[+A]    Function1[-A1,+A2]    ...    Function22[-A1,-A2,...,-A22,+A23]

一共23个,可以表示带0个参数到带22个参数的函数,后面将讲到的函数字面量(有些书也翻译成函数文本、匿名函数)在运行时也是转化成一个函数类的对象来进行调用的。这些函数类型都定义了 apply 方法,因此以函数调用的方式使用函数对象时将转而执行该对象的 apply 方法。

Note:定义普通函数时参数个数可以超过22个
  • 函数字面量、函数参数占位符、偏应用函数
        对于整数,我们可以方便对写成字面量对形式,如:99,程序运行过程中会自动转化成 Int 类型的实例;对于字符串,也可以写成字面量的形式,如:“Hello world”,程序运行过程中也会自动转化成 String 类的实例;同样,SCALA 中对于函数的使用也提供了便利的语法,可以这样定义一个函数字面量:
    val f = (i:Int) => i+1

scala 编译器会将它转化为一个 FunctionN 类的实例子,这里是 Function1[Int, Int] 类的实例。此时,如果想调用该函数,可以这样写:
    f(99)

Function类定义了一个相同参数列表的 apply 方法,以上调用会转化为 f.apply(99) ,而在此 apply 方法中封装了所定义的函数字面量的函数体。
       SCALA 的类型推断机制使得你能以更简洁大方式定义函数,如:
    0 to 9 map {i=>i+1}

0 to 9 产生的是一个Range类实例,由于编译器明确知道遍历的元素是Int,故此处可以利用类型推断而省略参数类型。要是还想更加简洁,就可以使用参数占位符语法,如:
    0 to 9 map {_+1}

这时参数列表都可以省略了,注意这种语法使用时{}只能将 _ 占位符使用在一个表达式中,并且此表达式中第一个 _ 代表第一个参数,第二个 _ 代表第二个参数,以此类推。注意在{}语句块中写多个语句结果也是最后一个表达式的值,并且这里不是传名参数,作为参数的语句块不会遍历过程的每次都执行,只会最开始执行一遍得出一个值,之后该值传给 map。如:
    0 to 9 map {println("Hello"); _+1}

这里会先计算{}语句块的值为 _+1,然后再调用该 Range 实例的 map 函数,所以只打印一次 “Hello”。
        利用占位符语法还可以定义偏应用函数,一般教程里面都是分开来说这两个概念的,感觉其实都是一种语法形式,最主要的是编译器最后编出来的东西都是 FunctionN 类的实例。所谓的偏应用函数,是在调用一个函数的时候没有给出所有的参数,如:
def func(i:Int, j:Int):Int = i + jval func1 = func _val func2 = func(_, 99)

如上代码,可以所有参数都不给出,也可以只给出一个或者几个参数。对于 func1 形式定义的偏应用函数,如果程序中某处确认需要一个该函数类型的实例时,连 _ 都可以省略了,如:
def incr(i:Int) = i + 10 to 9 map incr    // 这里实际等同于 0 to 9 map {incr _}

注意:def 定义的函数是不可以直接作为第一类值使用的,但是转化为偏应用函数之后就可以了,其实此时它已经被一个 FunctionN 类的实例包裹起来了。如:
val incr1 = incr

会报编译错误:
error: missing arguments for method incr

但是写成:
val incr1 = incr _

是可以正常编译的。前面讲过这种情况也可以视情况省略 _ 占位符,如:
val incr2:Int=>Int = incr  //这里等同于 val incr2:Int=>Int = incr _

这样也是可以的。

  • 函数闭包

闭包只有在函数中有自由变量时(也即函数中用到了此函数外定义的变量)才会产生。含有自由变量的函数被称为是开放的,通过绑定了自由变量就可以对函数进行关闭,编译器最终生成的函数实例中会有指向所绑定自由变量的引用。 如:

val base = 99def add(i:Int):Int = i + base

这里就会形成一个闭包,这时会将 base 变量绑定到该函数。同样,函数中对所绑定的自由变量的修改也可以被外界所感知,如:

var base = 99def add(i:Int):Int = {base -=1; i + base}

这样每一次调用函数 add,base 值都会被修改,可以用 Scala REPL 看下效果,这里就不详述了。





0 0