Scala函數的柯里化

在函數式編程中,函數是一等公民。 函數可以作爲參數傳入其他函數,函數的返回值也可以是函數,函數裏面也可以嵌套函數。這些高階函數在scala中被稱爲函數值。 閉包是函數值的特殊形式,因爲他會綁定到另外一個作用域上線文定義的變量上。

Scala的匿名函數:

匿名函數的語法很簡單: 就是箭頭左邊是參數列表,右邊是函數體。
比如:

val inc = (x:Int) => x + 1
scala> inc(7)
res10: Int = 8

也可以不設置參數列表:

scala> val out = () => print(10)
scala> out()
10

Scala函數的柯里化:

函數的柯里化是指將原來是兩個參數的函數變爲一個參數的過程,但是這個一個參數的函數返回的是以第二參數爲參數的匿名函數:
比如定義一個函數:

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

那麼把這個add函數變一下:

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

上面add的函數變化的過程就是函數的柯里化,調用過程如下:

scala> add(10)(1)
res18: Int = 11

add(10)(1)實際上是依次調用兩個普通的函數(非柯里化的函數)

這個柯里化的函數最先應該是演變成這麼如下的方法:

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

所以實際上調用第一次參數返回的應該是一個匿名函數,一個以傳遞Int類型的匿名函數,接着纔是函數的真正運算

scala> var reuslt = add(10)_
reuslt: Int => Int = $$Lambda$1065/164865953@1c411474

scala> reuslt(1)
res2: Int = 11
scala> add(10)(1)
res18: Int = 11

兩個參數的函數可以拆分,同理三個參數的函數同樣也可以柯里化:

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

scala> add(10)(10)(10)
res19: Int = 30

簡單看一個柯里化函數函數,集合中foldLeft()

override /*TraversableLike*/
def foldLeft[B](z: B)(op: (B, A) => B): B =
  foldl(0, length, z, op)

這個函數在集合中很有用,簡單分析下B表示泛型,後面傳遞一個B類型的參數Z,柯里化之後後面是一個匿名函數,傳遞兩個參數,返回一個B類型的參數,在看一下他的調用:

@tailrec
private def foldl[B](start: Int, end: Int, z: B, op: (B, A) => B): B =
  if (start == end) z
  else foldl(start + 1, end, op(z, this(start)), op)

用到了遞歸,如果開始位置和結束位置是相等,返回z遞歸到了終點。否則的話繼續遞歸,start每次加1,然後調用op這個匿名函數,this[start]取出集合中的數據進行函數操作,操作的結果就是下一次調用的參數Z,所以 我們可以利用這個folLeft求集合的最大值呀,最小值呀,求和啊 什麼的挺方便“:

val array = Array(1, 2, 3, 4, 5)
val result = array.foldLeft(0){(sum, element) => sum + element}
println(s"foldLeft = ${result}")

還有一個foldRight,跟這個類似,一個是從開始位置向後迭代(start + 1),一個是後往前迭代(end - 1):

override /*IterableLike*/
def foldRight[B](z: B)(op: (A, B) => B): B =
  foldr(0, length, z, op)
  @tailrec
private def foldr[B](start: Int, end: Int, z: B, op: (A, B) => B): B =
  if (start == end) z
  else foldr(start, end - 1, op(this(end - 1), z), op)

關於這兩個還有一個簡寫就是/: 和:\ 在scala中以:開頭或者結尾的都有特殊的含義的:

val array = Array(1, 2, 3, 4, 5)
val result = (0/:array)((sum, element) => sum + element)
println(s"foldLeft = ${result}")

上面這個是foldLeft的簡寫,同理 foldRight的簡寫:

val array = Array(1, 2, 3, 4, 5)
val result = (array:\0)((sum, element) => sum + element)
println(s"foldLeft = ${result}")

源碼如下:

def /:[B](z: B)(op: (B, A) => B): B = foldLeft(z)(op)
def :\[B](z: B)(op: (A, B) => B): B = foldRight(z)(op)

其實這個東西 有個名詞叫做方法名約定。 我猜 根據冒號的就近原則,調用的是冒號最近的那個實例,所以foldLeft調用的時候array在/:右邊,foldRight調用的時候array在;\左邊.

關於下劃線_:
_ 下劃線在scala中很有用,比如在初始化某一個變量的時候了下劃線代表的是這個變量的默認的值,
在函數中下劃線代表的是佔位符,用來表示一個函數值的參數,名字和類型都會被隱晦的指定了,當然如果Scala無法判斷下劃線代表的類型,那麼就可能要報錯了。這種情況下當然也可以給下劃線指定類型,那就不如用參數名了。

對於上面的foldLeft 你甚至可以這麼寫:

val array = Array(1, 2, 3, 4, 5)
val result = (array:\0)(_+_)
println(s"foldLeft = ${result}")

看起來是不是簡潔的多!

在這裏插入圖片描述

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