1. 程式人生 > >十. Scala函式詳解

十. Scala函式詳解

Scala函式詳解

1.Scala函式說明

函式是一組一起執行一個任務的語句。您可以把程式碼劃分到不同的函式中。如何劃分程式碼到不同的函式中是由開發者決定的,但是在邏輯劃分上,劃分通常是根據第個函式的某個特定功能決定的,以便實現模組化程式設計需要。
Scala有函式和方法,二者在語義上的區別很小。
* Scala方法是類的一部分,而函式是一個物件可以賦值給一個變數。換句話說在類中定義的函式就是方法。
* 我們可以在任何地方定義函式,甚至可以函式內定義函式(稱作為內嵌函式)。
* Scala的函式名可以包含以下特殊字元:+, ++, ~, &, -, –, , /,:等。

2.Scala函式宣告

Scala函式宣告方法如下:

  def functionName ([引數列表]) : [return type]

如果不寫等於號和方法主體,那麼方法會被隱式宣告為“抽像(abstract)”,它的型別也會隨之是一個抽象型別。

3.Scala函式定義

方法定義由一個def 關鍵字開始,緊接著是可選的引數列表,一個冒號”:” 和方法的返回型別,一個等於號”=”,最後是方法的主體。
Scala 函式定義格式如下:

def functionName ([引數列表]) : [return type] = {
   function body
   return [expr]
}

以上程式碼中 return type 可以是任意合法的 Scala 資料型別。引數列表中的引數可以使用逗號分隔。
以下函式的功能是將兩個傳入的引數相加並求和:

object add{
   def addInt( a:Int, b:Int ) : Int = {
      var sum:Int = 0
      sum = a + b

      return sum
   }
}

如果函式沒有返回值,可以返回為 Unit,這個類似於 Java 的 void, 例項如下:

object Hello{
   def printMe( ) : Unit = {
      println("Hello, Scala!"
) } }

4.Scala函式呼叫

Scala 提供了多種不同的函式呼叫方式:
以下是呼叫方法的標準格式:

functionName( 引數列表 )

如果函式使用了例項的物件來呼叫,我們可以使用類似java的格式 (使用 . 號):

[instance.]functionName( 引數列表 )

下面程式碼演示了定義與呼叫函式的例項:

itlocals-MacBook-Pro:function david.tian$ vim Test.scala
object Test{
        def main(args: Array[String]){
                println("The result of a + b: "+ add(5,7));
        }
        //定義函式
        def add(a:Int, b:Int) : Int = {
                var sum:Int = 0
                sum = a + b
                return sum
        }
}
itlocals-MacBook-Pro:function david.tian$ scalac Test.scala
itlocals-MacBook-Pro:function david.tian$ scala Test
The result of a + b: 12

5.Scala函式詳解

Scala也是一種函式式語言,所以函式是 Scala 語言的核心。以下一些函式概念有助於我們更好的理解 Scala 程式設計:

5.1.函式傳名呼叫(Call-by-Name)

Scala的直譯器在解析函式引數(function arguments)時有兩種方式:
* 傳值呼叫(call-by-value):先計算引數表示式的值,再應用到函式內部;
* 傳名呼叫(call-by-name):將未計算的引數表示式直接應用到函式內部

在進入函式內部前,傳值呼叫方式就已經將引數表示式的值計算完畢,而傳名呼叫是在函式內部進行引數表示式的值計算的。這就造成了一種現象,每次使用傳名呼叫時,直譯器都會計算一次表示式的值。

itlocals-MacBook-Pro:function david.tian$ vim TestCallByName.scala
object TestCallByName{
        def main(args: Array[String]){
                postponed(time());
        }

        def time()={
                println("獲取時間,單位為納秒")
                System.nanoTime
        }

        def postponed( t: => Long) ={
                println("這是在postponed方法內")
                println("現在時間是: " + t)
        }
}

// 以上例項中我們聲明瞭 postponed 方法, 該方法在變數名和變數型別使用 => 符號來設定傳名呼叫。執行以上程式碼,輸出結果如下:
itlocals-MacBook-Pro:function david.tian$ scalac TestCallByName.scala
itlocals-MacBook-Pro:function david.tian$ scala TestCallByName
這是在postponed方法內
獲取時間,單位為納秒
現在時間是: 151774779961653
itlocals-MacBook-Pro:function david.tian$ scala TestCallByName
這是在postponed方法內
獲取時間,單位為納秒
現在時間是: 151782152619580
itlocals-MacBook-Pro:function david.tian$ scala TestCallByName
這是在postponed方法內
獲取時間,單位為納秒
現在時間是: 151787019920538
itlocals-MacBook-Pro:function david.tian$

5.2.指定函式引數名

一般情況下函式呼叫引數,就按照函式定義時的引數順序一個個傳遞。但是我們也可以通過指定函式引數名,並且不需要按照順序向函式傳遞引數,例項如下:

itlocals-MacBook-Pro:function david.tian$ vim TestParamName.scala
object TestParamName{
        def main(args: Array[String]){
                printParam(b=5, a=7);
        }
        def printParam( a:Int, b:Int) = {
                println ("Values of a :" + a);
                println ("Values of b :" + b);
        }
}
itlocals-MacBook-Pro:function david.tian$ scalac TestParamName.scala
itlocals-MacBook-Pro:function david.tian$ scala TestParamName
Values of a :7
Values of b :5

5.3.函式 - 可變引數

Scala 允許你指明函式的最後一個引數可以是重複的,即我們不需要指定函式引數的個數,可以向函式傳入可變長度引數列表。
Scala 通過在引數的型別之後放一個星號來設定可變引數(可重複的引數)。例如:

itlocals-MacBook-Pro:function david.tian$ vim TestVariableParam.scala
object TestVariableParam{
        def main(args: Array[String]){
                printParams("david.louis.tian", "fab.yin", "josen.zhang");
        }
        def printParams(args:String*)={
                var i :Int = 0;
                for (arg <- args){
                        println("The ["+i+"] Parameter value = "+arg);
                }
        }
}
itlocals-MacBook-Pro:function david.tian$ scala TestVariableParam
The [0] Parameter value = david.louis.tian
The [0] Parameter value = fab.yin
The [0] Parameter value = josen.zhang

5.4.遞迴函式

遞迴函式在函數語言程式設計的語言中起著重要的作用。
Scala 同樣支援遞迴函式。
遞迴函式意味著函式可以呼叫它本身。
以上例項使用遞迴函式來計算階乘:

itlocals-MacBook-Pro:function david.tian$ vim TestRecursiveParam.scala
object TestRecursiveParam{
        def main(args: Array[String]){
                for (i <- 1 to 10){
                        println(i + " 的階乘為: = " + factorial(i));
                }
        }

        def factorial(n: BigInt): BigInt = {
                if ( n <= 1)
                        1
                else
                        n*factorial(n-1)
        }
}
itlocals-MacBook-Pro:function david.tian$ scalac TestRecursiveParam.scala
itlocals-MacBook-Pro:function david.tian$ scala TestRecursiveParam
1 的階乘為: = 1
2 的階乘為: = 2
3 的階乘為: = 6
4 的階乘為: = 24
5 的階乘為: = 120
6 的階乘為: = 720
7 的階乘為: = 5040
8 的階乘為: = 40320
9 的階乘為: = 362880
10 的階乘為: = 3628800

5.5.預設引數值

Scala 可以為函式引數指定預設引數值,使用了預設引數,你在呼叫函式的過程中可以不需要傳遞引數,這時函式就會呼叫它的預設引數值,如果傳遞了引數,則傳遞值會取代預設值。例項如下:

itlocals-MacBook-Pro:function david.tian$ vim TestDefaultParam.scala
object TestDefaultParam{
        def main(args: Array[String]){
                println( "返回值: " + add());
        }

        def add(a:Int=5, b:Int=7): Int = {
                var sum:Int = 0
                sum = a + b
                return sum
        }
}
itlocals-MacBook-Pro:function david.tian$ scalac TestDefaultParam.scala
itlocals-MacBook-Pro:function david.tian$ scala TestDefaultParam
返回值: 12

5.6.高階函式

高階函式(Higher-Order Function)就是操作其他函式的函式。
Scala 中允許使用高階函式, 高階函式可以使用其他函式作為引數,或者使用函式作為輸出結果。
以下例項中,apply() 函式使用了另外一個函式 f 和 值 v 作為引數,而函式 f 又呼叫了引數 v:

itlocals-MacBook-Pro:function david.tian$ vim TestHighOrder.scala
object TestHighOrder{
        def main(args: Array[String]){
                println( apply( layout, 10) )
        }

        //函式f和值v作為引數,而函式f又呼叫了引數v
        def apply(f: Int => String, v: Int) = f(v)

        def layout[A](x: A) = "[" + x.toString() +"]"
}
itlocals-MacBook-Pro:function david.tian$ scalac TestHighOrder.scala
itlocals-MacBook-Pro:function david.tian$ scala TestHighOrder
[10]

5.7.內嵌函式

我麼可以在 Scala 函式內定義函式,定義在函式內的函式稱之為區域性函式。
以下例項我們實現階乘運算,並使用內嵌函式:


object TestNested{
        def main(args: Array[String]){
                println(factorial(1))
                println(factorial(2))
                println(factorial(3))
                println(factorial(4))
                println(factorial(5))
        }

        def factorial(i: BigInt): BigInt ={
                def fact (i: BigInt, accumulator: BigInt): BigInt = {
                        if ( i<=1 )
                                accumulator
                        else
                                fact(i-1, i*accumulator)
                }
                fact(i,1)
        }
}
itlocals-MacBook-Pro:function david.tian$ scalac TestNested.scala
itlocals-MacBook-Pro:function david.tian$ scala TestNested
1
2
6
24
120

5.8.匿名函式

Scala 中定義匿名函式的語法很簡單,箭頭左邊是引數列表,右邊是函式體。
使用匿名函式後,我們的程式碼變得更簡潔了。
下面的表示式就定義了一個接受一個Int型別輸入引數的匿名函式:

var inc = (x:Int) => x+1

上述定義的匿名函式,其實是下面這種寫法的簡寫:

def add = new Function1[Int,Int]{
    def apply(x:Int):Int = x+1;
}

以上例項的 inc 現在可作為一個函式,使用方式如下:

var x = inc(7)-1

同樣我們可以在匿名函式中定義多個引數:

var mul = (x: Int, y: Int) => x*y

mul 現在可作為一個函式,使用方式如下:

println(mul(3, 4))

我們也可以不給匿名函式設定引數,如下所示:

var userDir = () => { System.getProperty("user.dir") }

userDir 現在可作為一個函式,使用方式如下:

println( userDir() )

例項:

itlocals-MacBook-Pro:function david.tian$ vim TestAnonymous.scala
object TestAnonymous{
        def main(args: Array[String]){
                println("乘積後的結果 = " + multi(1))
                println("乘積後的結果 = " + multi(2))
        }
        var factor = 3
        var multi = (i:Int) => i*factor
}
itlocals-MacBook-Pro:function david.tian$ scalac TestAnonymous.scala
itlocals-MacBook-Pro:function david.tian$ scala TestAnonymous
乘積後的結果 = 3
乘積後的結果 = 6

5.9.偏應用函式

Scala 偏應用函式是一種表示式,你不需要提供函式需要的所有引數,只需要提供部分,或不提供所需引數。
如下例項,我們列印日誌資訊:

itlocals-MacBook-Pro:function david.tian$ vim TestPartiallyApplied.scala

import java.util.Date

object TestPartiallyApplied{
        def main(args: Array[String]){
                var date=new Date()
                log(date,"log-message00000000001")
                Thread.sleep(1000)
                log(date,"log-message00000000002")
                Thread.sleep(1000)
                log(date,"log-message00000000003")
        }
        def log(date: Date, message:String) = {
                println(date + "--------"+message)
        }
}
itlocals-MacBook-Pro:function david.tian$ scalac TestPartiallyApplied.scala
itlocals-MacBook-Pro:function david.tian$ scala TestPartiallyApplied
Tue Apr 25 11:21:21 CST 2017--------log-message00000000001
Tue Apr 25 11:21:21 CST 2017--------log-message00000000002
Tue Apr 25 11:21:21 CST 2017--------log-message00000000003

例項中,log() 方法接收兩個引數:date 和 message。我們在程式執行時呼叫了三次,引數 date 值都相同,message 不同。
我們可以使用偏應用函式優化以上方法,繫結第一個 date 引數,第二個引數使用下劃線(_)替換缺失的引數列表,並把這個新的函式值的索引的賦給變數。以上例項修改如下:

itlocals-MacBook-Pro:function david.tian$ vim TestPartiallyAppliedTuned.scala
import java.util.Date

object TestPartiallyAppliedTuned{
        def main(args: Array[String]){
                var date=new Date()
                var logWithDateBound = log(date, _ :String)

                logWithDateBound("log-message00000000001")
                Thread.sleep(1000)
                logWithDateBound("log-message00000000002")
                Thread.sleep(1000)
                logWithDateBound("log-message00000000003")
        }
        def log(date: Date, message:String) = {
                println(date+"--------"+message)
        }
}
itlocals-MacBook-Pro:function david.tian$ scalac TestPartiallyAppliedTuned.scala
itlocals-MacBook-Pro:function david.tian$ scala TestPartiallyAppliedTuned
Tue Apr 25 11:27:31 CST 2017--------log-message00000000001
Tue Apr 25 11:27:31 CST 2017--------log-message00000000002
Tue Apr 25 11:27:31 CST 2017--------log-message00000000003

5.10.函式柯里化(Function Currying)

柯里化(Currying)指的是將原來接受兩個引數的函式變成新的接受一個引數的函式的過程。新的函式返回一個以原有第二個引數為引數的函式。
例項
首先我們定義一個函式:

def add(x:Int,y:Int)=x+y

那麼我們應用的時候,應該是這樣用:add(1,2)
現在我們把這個函式變一下形:

def add(x:Int)(y:Int) = x + y

那麼我們應用的時候,應該是這樣用:add(1)(2),最後結果都一樣是3,這種方式(過程)就叫柯里化。

實現過程
add(1)(2) 實際上是依次呼叫兩個普通函式(非柯里化函式),第一次呼叫使用一個引數 x,返回一個函式型別的值,第二次使用引數y呼叫這個函式型別的值。
實質上最先演變成這樣一個方法:

def add(x:Int)=(y:Int)=>x+y

那麼這個函式是什麼意思呢? 接收一個x為引數,返回一個匿名函式,該匿名函式的定義是:接收一個Int型引數y,函式體為x+y。現在我們來對這個方法進行呼叫。

val result = add(1)

返回一個result,那result的值應該是一個匿名函式:(y:Int)=>1+y
所以為了得到結果,我們繼續呼叫result。

val sum = result(2)

最後打印出來的結果就是3。

完整例項
下面是一個完整例項:

itlocals-MacBook-Pro:function david.tian$ vim TestCurrying.scala
object TestCurrying{
        def main(args:Array[String]){
                var s1:String = "Hello,"
                var s2:String = "Scala!"
                println("s1 + s2 =" + strconcat(s1)(s2))
        }
        def strconcat(s1:String)(s2:String) = {
                s1 + s2
        }
}
itlocals-MacBook-Pro:function david.tian$ scalac TestCurrying.scala
itlocals-MacBook-Pro:function david.tian$ scala TestCurrying
s1 + s2 =Hello,Scala!