高階函數

      在〈高階函數〉中尚無留言

函數型參數

函數中的參數,一般都是傳入資料型態。但在Python中,參數卻可以是另一個函數。這種將函數當成參數傳入,稱為高階函數。高階函數其是是數學的一個概念,比如 f(x)=x2 的導數為2x。也就是說導出的2x也是一個函數。

底下是Python的代碼,如果要測試,請在Pycharm裏編寫執行。

def c2f(t):#t是小數
return t*9/5+32
def f2c(t):#t是小數
return (t-32)*5/9
def temp(t, func):#t是小數,func是函數
return func(t)
if __name__=="__main__":
c=100
f=temp(c, c2f)
print(f'攝氏:{c}, 華氏:{f}')
f=212
c=temp(f, f2c)
print(f'華氏:{f}, 華氏:{c}')

“::” 運算子

Java不可以將函數當成參數傳入,但強大的kotlin卻可以。其實kotlin並不是徹底的改變其架構,而是使用了一個賤招~~利用 “::” 運算子將函數copy 新的一份,然後包裝成一個物件。此物件的型態為

(參數1, 參數2, ……) -> 返回值

底下的程式碼中,可以使用 val funObject : (Double)->Double = ::c2f,此物件有一個invoke()方法,此方法就是要開始執行 c2f 函數,而invoke裏的參數,就是要傳入c2f 的參數。如下所示。

fun main(args:Array<String>){
val funObject:(Double)->Double = ::c2f
println(funObject.invoke(100.0))

}
fun c2f(t:Double):Double{
return t*9/5+32
}

因為kotlin可以自動判斷型別,所以可以寫成

val funObject = ::c2f

另外funObject.invoke(100.0),可以把invoke省略,變成

funObject(100.0)

所以更精簡的寫法為如下所示

fun main(args:Array<String>){
val funObject=::c2f
println(funObject(100.0))

//或者更精簡成如下
println((::c2f)(100.0))
}
fun c2f(t:Double):Double{
return t*9/5+32
}

kotlin的高階函數

好了,即然函數(c2f)被包裝成 funObject 物件了,那麼此物件就可以傳入另一個函數 (temp) 當成參數。底下temp函數中,接收了t:Double 及 funObject: (Double)->Double 二個參數

fun temp(t:Double, funObject:(Double)->Double){}

然後在主程式調用時,必需寫成 : temp(funObject)

底下是完整的代碼

fun main(args:Array<String>){
var c=100.0
var f=temp(c, ::c2f)
println("攝氏:$c, 華氏:$f")

f=212.0
c=temp(f, ::f2c)
println("華氏:$f, 攝氏:$c")
}
fun temp(t:Double, funObject:(Double)->Double):Double{
return funObject(t)
}

fun c2f(t:Double):Double{
return t*9/5+32
}

fun f2c(t:Double):Double{
return (t-32)*5/9.0
}
結果:
攝氏:100.0, 華氏:212.0
華氏:212.0, 攝氏:100.0

這種東西好像是脫褲子放屁吼。在主程式只要寫

if __name__=='__main__':
c2f(100)
f2c(212)

對啦,以前初學程式的我也是這麼想的。後來到了更深的課題後,才發現為什麼Python要這麼麻煩。當然,因為好用,所以kotlin就整個把這個概念給copy過來了。

無返回值

如果函數型參數為返回值,就必需使用Unit

val funObject= (參數, ....)->Unit

匿名函數

先列出完整的原始碼,等會再作完整說明。

fun main(args:Array<String>){
var b1=Button("開始")
b1.click(::b1_click)
Button("結束").click(
fun(v:String):Unit{
println("${v}按鈕被按了")
}
)
}
fun b1_click(v:String){
println("${v}按鈕被按了")
}
class Button{
var name:String?=null
constructor(name:String){
this.name=name
}
fun click(job:(v:String)->Unit){
job.invoke(name!!)
}
}

Button類別

上述代碼中,先解析一下Button類別,裏面有一個click的方法。這個方法是指當按鈕被按下時要執行的動作。但我們卻在click的參數中接收另一個方法(job)。這個job是由主程式傳入的,所以很明顯的,按下去要作什麼事,不是Button決定,而是由主程式決定。所以我們又把這個寫法稱為回調,或指派。在Button類別中要啟動回調的方法為 job.invoke(String)。

class Button{
var name:String?=null
constructor(name:String){
this.name=name
}
fun click(job:(v:String)->Unit){
job.invoke(name!!)
}
}

主程式

主程式中,產生b1按鈕了,再將b1_click函數以 b1.click(::b1_click)包裝成物件傳入。

而第二顆按鈕的click所以傳入的方法,就更簡化為 b2.click(fun(v:String):Unit{})。請注意,fun後面沒有函數名稱,所以稱為匿名函數。

fun main(args:Array<String>){
var b1=Button("開始")
b1.click(::b1_click)
Button("結束").click(
//底下為匿名函數,沒有函數名稱
fun(v:String):Unit{
println("${v}按鈕被按了")
}
)
}
fun b1_click(v:String){
println("${v}按鈕被按了")
}

Lambda

一開始,可以Lambda當成是匿名函數的精簡寫法,如下所示,原本紅色的匿名函數可以改成藍色的部份。

先把click()內的匿名函數移出 “()”之外,然後加上 “{}”,{匿名函數的參數 -> 匿名函數的主体}

Button("結束").click(
fun(v:String):Unit{
println("${v}按鈕被按了")
}
)
//底下是Lambda寫法
Button("結束").click() {v:String->
println("${v}按鈕被按了")
}

大部份的匿名函數,都只有一個參數,所以只有一個參數時又可以把click的 “()” 省略,精簡成如下

Button("結束").click{v:String->
println("${v}按鈕被按了")
}

最後,可以把 v:String也刪除,然後用 it 代替

Button("結束").click{ 
println("${it}按鈕被按了")
}

匿名函數物件

上述的匿名函數,可以一省再省,是因為class Button裏的click有宣告參數型態及返回值型態。所以精簡成Lambda語法時,可以不用再註明其型態。

那如果是下面的匿名函數指定給某一變數時,又不一樣了。

var b3=fun(v:String):String{
return "finished"
}

寫成Lambda如下,不可以省略 v:String。另外,一定要把 “return” 刪除

var b3 = {v:String ->
"finished"
}

函數型返回值

todo

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *