眾所周知,scala作為一門極客型的函數(shù)式編程語言,支持的特性包括:
- 函數(shù)擁有“一等公民”身份;
- 支持匿名函數(shù)(函數(shù)字面量)
- 支持高階函數(shù)
- 支持閉包
- 部分應(yīng)用函數(shù)
- 柯里化
首先需要指出,在scala中有方法和函數(shù)對(duì)象兩種形態(tài),方法即是通過def關(guān)鍵字定義的函數(shù),而函數(shù)對(duì)象則是通過將方法轉(zhuǎn)換而來,或lambda賦值而來。
1. 從“一等公民”說起
很多稍微了解過函數(shù)式編程的人可能都聽說過“一等公民”這種說法,但卻很少有人能明明白白地說出究竟什么是“一等公民”。這里我做個(gè)類比你馬上就明白了:現(xiàn)實(shí)中,什么樣的人能被當(dāng)做一等公民?首先,他必須是個(gè)獨(dú)立的個(gè)體——依賴父母或朋友才能生存的人肯定不能被當(dāng)做公民,更不用說一等了;其次,這個(gè)人必須擁有足夠的自由——既能上九天攬?jiān)?,又可下五洋捉鱉,方才能是一等公民。對(duì)應(yīng)到我們的函數(shù)式編程,我們可以總結(jié)出幾個(gè)點(diǎn):
(1) 函數(shù)的定義和調(diào)用不依賴其他結(jié)構(gòu),例如C、python、js、scala,而反面典型就是java,因?yàn)閖ava的任何函數(shù)(方法)都必須定義在類、接口、枚舉(其實(shí)也是類)中,而且任何的方法調(diào)用都要通過對(duì)象、類的靜態(tài)方法或接口(jdk 1.8),方法不可能直接調(diào)用,必須依附于其他結(jié)構(gòu)而存在。所以這種情況下函數(shù)肯定不是“一等公民”。
(2)函數(shù)可以作為函數(shù)的參數(shù)、返回值,并可以對(duì)函數(shù)進(jìn)行變量賦值,而且函數(shù)的定義位置極度自由,任何代碼塊里又能定義函數(shù)。
現(xiàn)在我們?cè)賮砜磗cala,它完美地契合上邊所有的需求(但是注意,除了腳本形式的scala之外,其他的scala程序也只能包含在class或object中),scala中函數(shù)支持在函數(shù)內(nèi)部定義,而且使用lambda表達(dá)式定義的函數(shù)可以賦值給任何變量、常量,所有函數(shù)均可作為返回值、參數(shù)。
2. lambda表達(dá)式的學(xué)問
很多scala初學(xué)者都倒在了scala的lambda上,因?yàn)閟cala lambda的靈活多樣,導(dǎo)致很多時(shí)候你可能都看不懂。下面我們從最基本的講起:
最基本的:
<pre style="font-family: Courier New; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre-wrap; word-wrap: break-word;">val fun = (a:Int) => {a < 100 && a > 0}</pre>
當(dāng)r定義的參數(shù)為函數(shù)時(shí):
<pre style="font-family: Courier New; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre-wrap; word-wrap: break-word;">def fun1(f:String => Unit) = f("wangyalou")</pre>
我們可以方便地使用lambda傳入需要的函數(shù):
<pre style="font-family: Courier New; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre-wrap; word-wrap: break-word;">fun1((s:String)=>{println(s)})</pre>
注意了,一般人都不這么寫,因?yàn)樽鳛閰?shù)的lambda可以簡寫~~~~準(zhǔn)備好我要開始啰!首先,省略掉可以推斷出來的類型參數(shù):
<pre style="font-family: Courier New; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre-wrap; word-wrap: break-word;">fun1((s)=>{println(s)})</pre>
當(dāng)只有一個(gè)參數(shù)時(shí),=>前的()可?。?/p>
<pre style="font-family: Courier New; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre-wrap; word-wrap: break-word;">fun1(s=>{println(s)})</pre>
還可再簡化,scala中可以用_代替只出現(xiàn)一次的參數(shù):
<pre style="font-family: Courier New; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre-wrap; word-wrap: break-word;">fun1(println(_)) 或 fun1(println _)</pre>
最后,我們甚至連下劃線都可以不要了:
<pre style="font-family: Courier New; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre-wrap; word-wrap: break-word;">fun1(println)</pre>
注意,最后的情況我們是利用了編譯器支持lambda的“eta轉(zhuǎn)換”,即在表達(dá)式只有一個(gè)參數(shù),且整個(gè)執(zhí)行部分就是一個(gè)函數(shù)調(diào)用時(shí),可以直接寫函數(shù)名
插一句:eta擴(kuò)展(eta-expression)是另一個(gè)東西,指的是將一個(gè)普通方法轉(zhuǎn)換為函數(shù)對(duì)象的過程:
<pre style="font-family: Courier New; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre-wrap; word-wrap: break-word;">val b= too(,,_)
val b= too _ //這也可以?是的
val b : (Int,Int,Int) =>Int = foo</pre>
其中too為一個(gè)參數(shù)為3個(gè)Int的方法
但是,too(,,1)一定不是eta擴(kuò)展!
下劃線的用法博大精深,這里再給出一些例子:
[
](javascript:void(0); "復(fù)制代碼")
<pre style="font-family: Courier New; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre-wrap; word-wrap: break-word;">例1:lambda作為參數(shù),可代替只用一次的參數(shù),且省掉“=>”
val nums = Array(1,2,3,4)
nums.filter(>2)
運(yùn)行結(jié)果:
res31: Array[Int] = Array(3, 4)
</pre>
<pre style="font-family: Courier New; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre-wrap; word-wrap: break-word;">例2:lambda作為參數(shù),_可代替只用一次的參數(shù),且省掉“=>”</pre>
scala> def foo(f:(Int,Int)=>Int)(a:Int,b:Int) = f(a,b)
foo: (f: (Int, Int) => Int)(a: Int, b: Int)Int
scala> foo(+)(3,4)
res33: Int = 7
<pre style="font-family: Courier New; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre-wrap; word-wrap: break-word;">例3:lambda作為函數(shù)定義,可代替只用一次的參數(shù),且省掉“=>”,但這時(shí)要加上類型,因?yàn)檫@里無法推斷出“”的類型</pre>
val b = (:Int) + (:Int)
b(1,2)
運(yùn)行結(jié)果:
res32: Int = 3
[
](javascript:void(0); "復(fù)制代碼")
3. 部分應(yīng)用函數(shù)(偏函數(shù))
一個(gè)例子足以說清楚:
<pre style="font-family: Courier New; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre-wrap; word-wrap: break-word;">scala> def foo(a:Int, b:Int, c:Boolean) = if(c) a+b else a-b
foo: (a: Int, b: Int, c: Boolean)Int
scala> val foom = foo(:Int,:Int,false) foom: (Int, Int) => Int = <function2></pre>
類似于python中的偏函數(shù),這里將某個(gè)參數(shù)確定,其他參數(shù)用"_"代替并指明其類型,注意一定要指明類型?。。?!不然就成了eta擴(kuò)展失敗的案例了!!