【scala 筆記(5)】 Scala中的函數

scala 除了支持方法(方法是對object的操作,也就是java中定義的class的方法)外還支持函數的; C/C++、Python 也有函數, 而在Java中我們只能通過靜態方法來模擬。

函數

函數定義

要定義一個函數,你要給出函數的名稱、參數和函數體 ; 例如:

def fac(x:Int) = {
      var result = 1
      for (i <- 1 to x) result = result * i
      result
    }

你必須給出所有參數的類型。 不過,只要函數不是遞歸的,你就不需要指定返回類型, scala 會根據 = 號右邊的表達式推導出返回的類型。或進行顯式定義爲:

def fac(x:Int) :Int = {
      var result = 1
      for (i <- 1 to x) result = result * i
      result
    }

注: 本例中不需要使用return,當然我們也可以使用return來立即從函數中退出, 不過在scala中使用return並不常見, 我們最好要適應沒有return的日子, 我們可以把 return 當做 函數版本的 break 語句, 僅在需要時進行調用。

默認參數和帶名參數

我們在調用某些函數時不顯式的給出所有參數值,對於這些函數可以使用默認值:

scala> def show(msg:String, left:String="[", right:String="]"){
     |       println("%s%s%s".format(left, msg, right))
     |     }
show: (msg: String, left: String, right: String)Unit

scala> show("hello")    // 調用方式1
[hello]

scala> show("hello", "<", ">") // 調用方式2
<hello>

scala> show(left ="info[", msg ="hello") // 調用方式3
info[hello]

注: 這裏定義的函數在Scala中被稱爲過程; Scala 對於不返回值的函數有特殊的表示法, 如果函數體包含在 {} 中但前面沒有 = 號, 那麼返回的類型爲Unit。 這種函數被稱爲 過程(procedure) , 也可以顯式定義爲 Unit返回類型:

def show( ... ): Unit = {
    ......
} 

不定長參數

實現一個可變長參數的函數, 例如:

scala> def show(args: Any *){
     |       for (arg <- args){
     |         arg match {
     |           case i: Int =>
     |             println("type int: %d".format(i))
     |           case s: String =>
     |             println("type string: %s".format(s))
     |           case _ =>
     |             println("unknow type arg ")
     |         }
     |       }
     |     }
show: (args: Any*)Unit

scala> show(1, "2", 3.0)
type int: 1
type string: 2
unknow type arg

scala> show(1 to 3: _*) // 追加 : _* 告訴編譯器希望參數被當做參數序列處理
type int: 1
type int: 2
type int: 3

作爲值的函數

在scala中, 函數時 “頭等公民” ,就和數字一樣。 你可以在變量中存放函數(有點像C++中的函數指針):

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

scala> val func : (Int, Int) => Int = add
func: (Int, Int) => Int = $$Lambda$1502/1003761391@5fa842b5

scala> val func1 = add _ // _ 佔位符
func1: (Int, Int) => Int = $$Lambda$1503/817093103@5d32d8f

匿名函數

Scala 中定義匿名函數的語法很簡單,箭頭左邊是參數列表,右邊是函數體。使用匿名函數後,我們的代碼變得更簡潔了。

scala> val func = (x: Int) => 2*x
func: Int => Int = $$Lambda$1517/285812804@2640893e

scala> val func = 2*(_:Int)         // 精簡版, 通過 _ 佔位符指定類型來幫助函數推導出返回值
func: Int => Int = $$Lambda$1556/399984046@787aa9f1

scala> val func: Int=>Int = 2*_     // 精簡版, 通過指定func的函數的類型
func: Int => Int = $$Lambda$1557/143470731@6ed810f3

原型

scala> def func = new Function1[Int, Int] {
     |       def apply(v: Int) : Int= {
     |         2 * v
     |       }
     |     }
func: Int => Int

高階函數

高階函數(Higher-Order Function)就是操作其他函數的函數。Scala 中允許使用高階函數, 高階函數可以使用其他函數作爲參數,或者使用函數作爲輸出結果。

例如: 常用要熟悉的高階函數

scala> val lst = 1::2::3::Nil
lst: List[Int] = List(1, 2, 3)

scala> lst.map(2*_)
res35: List[Int] = List(2, 4, 6)

scala> lst.foreach(println)
1
2
3

看下高階函數如何定義通過源碼:

/** 我們最關心的僅是 (f: A => B)函數  
 *   A:表示List[T] T的類型, 
 *   B:表示map後的List[T] T的類型, 取決於傳入的函數定義
 */ 

final override def map[B, That](f: A => B)(implicit bf: CanBuildFrom[List[A], B, That]): That = {
    if (bf eq List.ReusableCBF) {
      if (this eq Nil) Nil.asInstanceOf[That] else {
        val h = new ::[B](f(head), Nil)
        var t: ::[B] = h
        var rest = tail
        while (rest ne Nil) {
          val nx = new ::(f(rest.head), Nil)
          t.tl = nx
          t = nx
          rest = rest.tail
        }
        h.asInstanceOf[That]
      }
    }
    else super.map(f)
  }

/** 這個比較簡單, 直接進行List遍歷調用 f函數 
 */ 
@inline final override def foreach[U](f: A => U) {
    var these = this
    while (!these.isEmpty) {
      f(these.head)
      these = these.tail
    }
  }

閉包

在函數中可以(嵌套)定義另一個函數時,如果內部的函數引用了外部的函數的變量,則可能產生閉包。運行時,一旦外部的 函數被執行,一個閉包就形成了,閉包中包含了內部函數的代碼,以及所需外部函數中的變量的引用。其中所引用的變量稱作上值(upvalue)。

閉包一詞經常和匿名函數混淆。這可能是因爲兩者經常同時使用,但是它們是不同的概念。

scala> val flag = 2
flag: Int = 2

scala> def func(x:Int) = (v: String) =>{
     |       for(i <- 1 to flag*x){
     |         println(v * i)
     |       }
     |     }
func: (x: Int)String => Unit

scala> val f = func(1)
f: String => Unit = $$Lambda$1574/696606344@dcd6f19

scala> f("*")
*
**

scala> val f = func(2)
f: String => Unit = $$Lambda$1574/696606344@74d16083

scala> f("*")
*
**
***
****

柯里化(Currying)

柯里化把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數而且返回結果的新函數。

可以換個邏輯理解:
看做是一個數學問題,一個逐次消元的過程。當把函數的元全消掉,就得到了值,值就是零元函數。 例如:
- 二元函數 f(x,y) = x+y
- 在y=1時, g(x) = f(x,1) = x+1
- 在x=2時, g(2) = 2+1=3

接受兩個參數的函數

// 函數類型 (Int, Int) => Int
scala> def add(x:Int, y:Int) :Int = {
     |       x+y
     |     }
add: (x: Int, y: Int)Int

柯里化

// 函數類型 Int => (Int => Int)
scala> def add(x:Int) = (y:Int) => x+y
add: (x: Int)Int => Int

那麼如何從 函數轉換爲 柯里化?

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

scala> val f1= add _
f1: (Int, Int) => Int = $$Lambda$1580/18999287@57715987

scala> f1.curried // 柯里化後的函數形式
res39: Int => (Int => Int) = scala.Function2$$Lambda$1433/579868782@12855741
  • 關於 curried 函數

// 看 scala Function2 的定義, 它會將自己轉換一個柯里化版本的函數

trait Function2[@specialized(scala.Int, scala.Long, scala.Double) -T1, @specialized(scala.Int, scala.Long, scala.Double) -T2, @specialized(scala.Unit, scala.Boolean, scala.Int, scala.Float, scala.Long, scala.Double) +R] extends AnyRef { self =>
  /** Apply the body of this function to the arguments.
   *  @return   the result of function application.
   */
  def apply(v1: T1, v2: T2): R
  /** Creates a curried version of this function.
   *
   *  @return   a function `f` such that `f(x1)(x2) == apply(x1, x2)`
   */
  @annotation.unspecialized def curried: T1 => T2 => R = {
    (x1: T1) => (x2: T2) => apply(x1, x2)
  }
  /** Creates a tupled version of this function: instead of 2 arguments,
   *  it accepts a single [[scala.Tuple2]] argument.
   *
   *  @return   a function `f` such that `f((x1, x2)) == f(Tuple2(x1, x2)) == apply(x1, x2)`
   */

  @annotation.unspecialized def tupled: Tuple2[T1, T2] => R = {
    case Tuple2(x1, x2) => apply(x1, x2)
  }
  override def toString() = "<function2>"
}

那麼問題來了,繞那麼大的圈, 柯里化有什麼實用場景呢?

  • 例如 控制抽象
 def until(condition: => Boolean)(block: => Unit): Unit = {
      if ( !condition ){
        block
        until(condition)(block)
      }
    }

var flag = 3
// 柯里化
until( flag<0 ) {
      println(flag)
      flag -= 1
    }

// 如果是接受多參函數
// def until(condition: => Boolean, block: => Unit): Unit = { ... }
// until( ... ,  { .... } )

雖然沒有柯里化也可以實現相應功能,看起來可能並不美觀,但柯里化並不是看起來美觀那麼簡單, 而是函數式編程的一個新的概念。

優點:
- 代碼複用;
- 代碼邏輯設計分層;
- 抽象計算等;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章