天天看點

泛函程式設計(4)-深入Scala函數類

既然是泛函程式設計,多了解一下函數自然是免不了的了:

方法(method)不等于函數(function)

方法不是函數但可以轉化成函數;可以手工轉換或者由編譯器(compiler)在适當的情況下自動轉換。反向轉換則不然;函數是無法轉換到方法的。先看看下面的例子:

amethod 與 afunction 在類型上是不同的。再看看下面:

引用方法必須提供完整的參數清單,引用函數則無須。把方法轉換成函數呢?在參數位置用 _ 來進行轉換:

amethod轉換成函數tofunctions後具備了函數的特性。

我們稱函數為“頭等類值”(first class value),可以當作高階函數的參數或傳回值。但方法不是“頭等類值”,不能當作參數。那麼怎麼解釋下面例子裡的代碼呢?

在這裡abs, factorial都是方法。傳入高階函數formatresult中能行嗎?下面是運作後的結果:

沒出錯呀。難道方法是可以當作傳入參數的嗎?實際上這段程式在編譯的時候由編譯器自動進行了轉換。scala的編譯器能針對需要函數的地方把方法轉換成函數。

函數就是普通的對象

下面是一個函數文本:

編譯時編譯器會把它轉換成下面的代碼:

這裡function2是scala語言标準類對象,res4(1+2) ===>  addthem.apply(1,2)

多态函數

為了示範scala的多态函數,我們先從下面的一個例子開始:從一個整數數組中找出第一個比對數的位置:

從一個字串數組中找出第一個比對字串的位置:

這個函數與上面整數數組例子有許多相似之處,或者說基本上是一緻的。這樣我們可以通過多态函數把共通點抽象出來:

findfirsta是個多态函數。a是一個類型變量。我們可以說findfirsta是個針對類型變量a的多态函數。注意我們在findfirsta增加了一個參數清單- (equ: (a,a) => boolean)。這是因為我們還無法确定a的類型。那麼我們必須提供a類型的對比函數。我們可以用findfirsta針對整數、字串進行操作。我們也可以對其它類型進行操作,隻要我們能提供那種類型的比較函數。

泛函程式設計說的白點就是擺弄函數。把函數擺過來弄過去的就完成了程式設計的過程。從下面的例子可以一探端倪:

純函數是可以部分作用(partially apply)的:對一個多入參函數可以分多次每次作用(apply)一個參數

通過函數partialapply可以把一個兩個入參的函數f分分兩次作用它的參數:引用partialapply是作用參數a,形成一個需要參數b的函數。

兩個參數作用(apply)了其中一個,是以稱為部分作用。該如何實作:

我們知道partialapply的結果是一個入參b傳回c的函數。是以想辦法從比對類型款式上着手。可以直接用一個函數文本表達這個結果:給我一個b=b,我傳回給你一個c=f(a,b);一個典型的lambda表達式。用一個實際的例子來示範:

addtwoparams是一個兩個入參的函數,applyonce是向addtwoparams作用了一個參數2後産生的函數,再用參數5再對applyonce作用後結果等于7. = addtwoparams(2,5)。為什麼費那麼大的勁把函數變來變去呢?實際上這種函數變形在泛函程式設計中很普遍,是函數組合(functional composition)必須掌握的技巧。

函數變形在泛函程式設計中是常用的技巧。下面的curry function就是把一個n個輸入參數的函數變成一個參數的n次作用:

f(a,b,c,...n) = f(a)(b)(c)...(n) = a => b => c => ... => n

函數currytwo把一個兩個參數的函數轉變成一個參數傳回另一個同樣是一個參數的函數。用函數文本實作currytwo後再使用一下來示範(注意傳回的類型款式):

把addtwoparams轉成curriedfunction int=>(int=>int)。可以馬上看到需要填寫兩次輸入參數。我們遇到這種函數變形的問題時通常會用函數文本嘗試比對函數的結果類型款式(type signature)。

uncurry是curry的反向:把curry函數返還到多參數函數:a => b => c => ... => n  = f(a,b,c,...n)

結合上面的例子示範uncurry函數實作和使用:

uncurriedfunction又變回去了。

 最後,示範一個函數組合的例子:

compose是将f和g兩個函數組合成另一個函數。看看下面實作和使用示範:

把fadd,fmul組合起來形成了一個新的函數。multhenadd(2) = (2 * 5) + 2

如果再寫的形象一點:

注意compose右邊關聯的(right hand associate):fadd compose fmul 中先運算fmul把結果輸入fadd進行運算。設計另一個左邊關聯函數andthen:

想想這裡面的意義:fadd和fmul可能都是幾千行代碼的大函數,而我們能夠很簡潔地把它們連接配接起來,隻需要把類型比對起來就行了。