Scala函數的柯里化

頭條號:浩渺煙波
關注可瞭解更多大數據、Android技術,黑科技,以及理財,問題或建議,請頭條號留言;


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

Scala的匿名函數:

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

<code>valinc=(x:Int)=>x+1
scala>inc(7)
res10:Int=8
/<code>

也可以不設置參數列表:

<code>scala>valout=()=>print(10)
scala>out()
10
/<code>


Scala函數的柯里化

懵逼沒


Scala函數的柯里化:

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

<code>defadd(x:Int,y:Int)=x+y
/<code>

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

<code>defadd(x:Int)(y:Int)=x+y
/<code>

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

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

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

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

<code>defadd(x:Int)=(y:Int)=>x+y
/<code>

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

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

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

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

<code>scala>defadd(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
/<code>


Scala函數的柯里化


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

<code>override/*TraversableLike*/
deffoldLeft[B](z:B)(op:(B,A)=>B):B=
foldl(0,length,z,op)
/<code>

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

<code>@tailrec
privatedeffoldl[B](start:Int,end:Int,z:B,op:(B,A)=>B):B=
if(start==end)z
elsefoldl(start+1,end,op(z,this(start)),op)
/<code>

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

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

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

<code>override/*IterableLike*/
deffoldRight[B](z:B)(op:(A,B)=>B):B=
foldr(0,length,z,op)
@tailrec
privatedeffoldr[B](start:Int,end:Int,z:B,op:(A,B)=>B):B=
if(start==end)z
elsefoldr(start,end-1,op(this(end-1),z),op)
/<code>

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

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

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

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

源碼如下:

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

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

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

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

<code>valarray=Array(1,2,3,4,5)
valresult=(array:\\0)(_+_)
println(s"foldLeft=${result}")
/<code>

看起來是不是簡潔的多!


分享到:


相關文章: