Scala學習筆記——函數式編程

在函數式編程中,函數式第一等級的值,就像數據變量的值一樣,你可以從函數中組合形成新函數(如`tan(x)=sin(x)/cos(x))`,可以將函數賦值給變量,也可以將函數作爲參數傳遞給其它函數,還可以將函數作爲其它函數的返回值。

當一個函數採用其它函數作爲變量或返回值時,它被稱爲高階函數。

object FactorialApp extends App {

  def factorial(i: Int): Long = {
    def fact(i: Int, accumulator: Int): Long = {
      if (i <= 1) accumulator
      else fact(i - 1, i * accumulator)
    }
    fact(i, 1)
  }

  (0 to 5) foreach (i => println(factorial(i)))

  var sum = (1 to 5) filter (_ % 2 == 0) map (_ * 2) reduce (_ + _)
  var fact = (1 to 5) filter (_ > 4) map (_ * 2) reduce (_ * _)
  print(sum)
  print(fact)
}

閉包

 //閉包
  var factor = 2
  val multiplier = (i: Int) => i * factor
  var b = (1 to 10) filter (_ > 4) map multiplier reduce (_ * _)
  println(b)
  FactorialApp.factor = 3
  var c = (1 to 10) filter (_ > 4) map FactorialApp.multiplier reduce (_ * _)
  println(c)

偏函數

 偏作用函數是一個表達式,帶部分而非全部參數列表的函數。返回值是一個新函數,新函數負責攜帶剩下的參數列表。 偏函數則是帶參數的函數,並未對該類型的所有值都有定義。偏函數的字面量語法由包圍在花括號中的一個或多個case語句構成:

object PartialFunctionApp extends App {

  def cat1(s1: String)(s2: String) = s1 + s2

  //偏應用函數
  val hello = cat1("hello ") _

  println(hello("world"))
  println(cat1("hello ")("world"))


  //偏應用函數
  def add(x: Int, y: Int) = x + y

  val addOne = add(1, _: Int)

  println(addOne(4)) // 5
  println(addOne(6)) // 7


  val inverse:PartialFunction[Double, Double] = {
    case d if d != 0.0 => 1.0 /d
  }

  println(inverse(2.0))

}
Curry化的函數

Curry將一個帶有多個參數的函數轉換爲一系列函數,每個函數都只有一個參數

package base.functionT


object CurryApp extends App {

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

  //add函數柯里化之後:
  //  def add(x: Int) = (y: Int) => x + y
  //簡化爲
  //  def add(x: Int)(y: Int) = x + y

  val add1 = (add _).curried

  println(add1(2)(3)) // 5
  println(add1(2) {
    5
  }) // 7


  def multiplier(i: Int)(factor: Int) = i * factor

  val byFive = multiplier(5) _
  val byFour = multiplier(4) _
  println(byFive(5))
  println(byFour(5))


  def map[A, B](xs: List[A])(func: A => B) = xs.map {
    func(_)
  }

  // List[String] = List(11, 21, 31)
  map(List(1, 2, 3)) {
    x => x + "1"
  }

}

方法與函數

方法:方法指的是定義在類中的方法
函數:函數在scala中代表1個類型和一個對象,方法卻不會,方法只會出現在類中。

def max(x: Int, y: Int): Int = if (x > y) x else y
def max = (x: Int, y: Int) => if (x > y) x else y 

兩者都定義爲「方法(Method)」,但後者返回了一個函數(Function)類型。因此,後者常常也被習慣地稱爲函數(Function)。首先,它們兩者可以具有相同的調用形式:max(1, 2)。但對於後者,調用過程實際上包括了兩個子過程。

首先調用max返回(Int, Int) => Int的實例;
然後再在該函數的實例上調用apply方法,它等價於:

max.apply(1, 2)

其次,兩者獲取函數值的方式不同。後者可以直接獲取到函數值,而對於前者需要執行η擴展才能取得等價的部分應用函數。

val f = max _

此時,f也轉變爲(Int, Int) => Int的函數類型了。實施上,對於上例,η擴展的過程類似於如下試下。

val f = new (Int, Int) => Int {
  def apply(x: Int, y: Int): Int = max(x, y)
}

val與def

def用於定義方法,val定義值。對於「返回函數值的方法」與「直接使用val定義的函數值」之間存在微妙的差異,即使它們都定義了相同的邏輯。例如:

val max = (x: Int, y: Int) => if (x > y) x else y 
def max = (x: Int, y: Int) => if (x > y) x else y

語義差異

 雖然兩者之間僅存在一字之差,但卻存在本質的差異。
 def用於定義「方法」,而val用於定義「值」。
def定義的方法時,方法體並未被立即求值;而val在定義時,其引用的對象就被立即求值了。def定義的方法,每次調用方法體就被求值一次;而val僅在定義變量時僅求值一次。
例如,每次使用val定義的max,都是使用同一個函數值;也就是說,如下語句爲真。

max eq max   // true

而每次使用def定義的max,都將返回不同的函數值;也就是說,如下語句爲假。

max eq max   // false

其中,eq通過比較對象id實現比較對象間的同一性的。

類型參數

val代表了一種餓漢求值的思維,而def代表了一種惰性求值的思維。但是,def具有更好可擴展性,因爲它可以支持類型參數。

def max[T : Ordering](x: T, y: T): T = Ordering[T].max(x, y)

lazy惰性

def在定義方法時並不會產生實例,但在每次方法調用時生成不同的實例;而val在定義變量時便生成實例,以後每次使用val定義的變量時,都將得到同一個實例。
lazy的語義介於def與val之間。首先,lazy val與val語義類似,用於定義「值(value)」,包括函數值。

lazy val max = (x: Int, y: Int) => if (x > y) x else y 

其次,它又具有def的語義,它不會在定義max時就完成求值。但是,它與def不同,它會在第一次使用max時完成值的定義,對於以後再次使用max將返回相同的函數值。

參數傳遞

Scala存在兩種參數傳遞的方式。

  • Pass-by-Value:按值傳遞
  • Pass-by-Name:按名傳遞
    按值傳遞
    默認情況下,Scala的參數是按照值傳遞的。
def and(x: Boolean, y: Boolean) = x && y

對於如下調用語句:

and(false, s.contains("horance"))

表達式s.contains("horance")首先會被立即求值,然後纔會傳遞給參數y;而在and函數體內再次使用y時,將不會再對s.contains("horance")表達式求值,直接獲取最先開始被求值的結果。

傳遞函數

將上例and實現修改一下,讓其具有函數類型的參數。

def and(x: () => Boolean, y: () => Boolean) = x() && y()

其中,() => Boolean等價於Function0[Boolean],表示參數列表爲空,返回值爲Boolean的函數類型。

調用方法時,傳遞參數必須顯式地加上() =>的函數頭。

and(() => false, () => s.contains("horance"))

此時,它等價於如下實現:

and(new Function0[Boolean] { 
  def apply(): Boolean = false
}, new Function0[Boolean] {
  def apply(): Boolean = s.contains("horance")
}

此時,and方法將按照「按值傳遞」將Function0的兩個對象引用分別傳遞給了x與y的引用變量。但時,此時它們函數體,例如s.contains("horance"),在參數傳遞之前並沒有被求值;直至在and的方法體內,x與y調用了apply方法時才被求值。
也就是說,and方法可以等價實現爲:

def and(x: () => Boolean, y: () => Boolean) = x.apply() && y.apply()
按名傳遞

通過Function0[R]的參數類型,在傳遞參數前實現了延遲初始化的技術。但實現中,參數傳遞時必須構造() => R的函數值,並在調用點上顯式地加上()完成apply方法的調用,存在很多的語法噪聲。

因此,Scala提供了另外一種參數傳遞的機制:按名傳遞。按名傳遞略去了所有()語法噪聲。例如,函數實現中,x與y不用顯式地加上()便可以完成調用。

def and(x: => Boolean, y: => Boolean) = x && y

其次,調用點用戶無需構造() => R的函數值,但它卻擁有延遲初始化的功效。

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