導讀 | 本篇文章主要介紹Kotlin函數的用法,以及自己對函數式程式設計的一些理解。並且會和Python,C 做一些比較。 |
自從Google爸爸宣布Kotlin為自己的乾兒子之後,Kotlin被各大社區炒的火熱。
如果你對Kotlin文法一無所知,推薦先閱讀官方文件或中文站(https://www.kotlincn.net/docs/reference/)之後再看這篇文章會有更深刻的理解。
下面是維基百科上對於函數式程式設計的定義:
函數式程式設計(英文:functional programming)或稱函數程式設計,又稱泛函編程,是一種程式設計範式,它將電腦運算視為數學上的函數計算,並且避免使用程式狀態以及易變對象。函數程式語言最重要的基礎是λ演算(lambda calculus)。而且λ演算的函數可以接受函數當作輸入(引數)和輸出(傳出值)。
下面是關於高階函數的定義:
在數學和電腦科學中,高階函數是至少滿足下列一個條件的函數:接受一個或多個函數作為輸入,輸出一個函數
不難推論出函數式程式設計最重要的基礎是高階函數。也就是支援函數可以接受函數當作輸入(引數)和輸出(傳出值)。
函數作為Kotlin中的一級公民可以像其他物件一樣作為函數的輸入與輸出,這也就是Java程式設計師轉到Kotlin覺得變化最大,最難理解的一點。如果你之前學過Python或C 11可能會對此比較容易接受。這也是為什麼本文以介紹Kotlin的函數及函數式程式設計為主。
Kotlin 函數下面是Kotlin中一般的函數定義,和Java不同的是函數形參,回傳值型別後。函數體可以用等號賦值來為函數定義,這裡也可以看出函數和變數的平等性。
fun main(args: Array) { var s = sum(1,2) var m = multi(2,3) var x = maxOf(3,4) } fun sum(a: Int, b: Int): Int { return a + b } fun multi(a: Int, b: Int): Int = a * b fun maxOf(a: Int, b: Int): Int = if (a > b) a else b
另外Kotlin也支援函數預設參數,拓展函數,中綴表達式,以下是簡單的例子:
fun main(args: Array) { isBiggerThan(2) isBiggerThan(2, 5) var s = "a".isLetter() var a = 1 add 2 } fun isBiggerThan(a: Int, b: Int = 0) { return a > b } //拓展函数 fun String.isLetter(): Boolean { return matches(Regex("^[a-z|A-Z]$")) } //拓展函数,中缀表达式 infix fun Int.add(x: Int): Int { return this + x }
支援預設參數的函數可以減少函數的重載。
String物件中本沒有判斷是否為字母的方法,在Java中我們一般會定義一些Utils方法,而在Kotlin中可以定義類別的拓展函數。
第二個例子是為Int類別定義了一個拓展函數,並且該拓展函數以中綴表達式表示,給予了開發者定義類似關鍵字的權利。
例如我們可以這樣建立一個map物件:
val kv = mapOf("a" to 1, "b" to 2)
這裡的to就是一個中綴表達式,定義如下:
public infix fun<A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
Pair就是Map中儲存的對象,所以你也可以這樣建立
val kv = mapOf(Pair("a", 1), Pair("b", 2))
在Python中如果我們想讓函數傳回多個值,可以回傳一個元組,Kotlin基於解構原則也可以實現類似的功能:
fun main(args: Array) { val (index, count) = findWhere("abcabcabcabc", 'c') } fun findWhere(str: String, findChar: Char): Pair<Int, Int> { var index = -1 var count = 0 for ((i, v) in str.withIndex()) { if (v == findChar) { if (index == -1) { index = i } ++count } } return Pair(index, count) }
自訂物件如何支援解構請查看官方文檔,map支援解構,所以可以像下面這樣遍歷:
for ((k, v) in map) { print("$k -> $v, ") }
「Lambda 表達式」(lambda expression)是一個匿名函數,Lambda表達式是基於數學中的λ演算得名,直接對應於其中的lambda抽象(lambda abstraction),是一個匿名函數,即沒有函數名的函數。 Lambda表達式可以表示閉包(注意和數學傳統意義上的不同)。
Python中的lambda表達式:
add = lambda x, y:x+y
C 中的lambda:
[](int x, int y) -> int{ return x + y; }
Kotlin中的lambda:
var add = {x: Int, y: Int -> x + y}
Kotlin 作為一個強型別語言還是比較簡潔的。
我們可以這樣使用一個lambda表達式:
fun main(args: Array) { val sumLambda = {a: Int, b: Int -> a + b} sumLambda(1, 2) }
它可以像函數一樣使用()調用,在kotlin中操作符是可以重載的,()操作符對應的就是類別的重載函數invoke()。
你還可以想下面這樣定義一個變數:
val numFun: (a: Int, b: Int) -> Int
它不是一個普通的變量,它必須指向一個函數,並且函數簽名必須一致:
fun main(args: Array) { val sumLambda = {a: Int, b: Int -> a + b} var numFun: (a: Int, b: Int) -> Int numFun = {a: Int, b: Int -> a + b} numFun = sumLambda numFun = ::sum numFun(1,2) } fun sum(a: Int, b: Int): Int { return a + b }
可以看到這個變數可以等於一個lambda表達式,也可以等於另一個lambda表達式變量,還可以等於一個普通函數,但是在函數名前需要加上(::)來取得函數參考。
這個類似C 中的函數指針,然而在Python中可以直接使用函數名稱作為函數引用,以下是c 函數指針的例子:
#include using namespace std; void swap(int &x, int &y); int main(int arg, char* args[]) { int x = 10; int y = 20; void (*methodPtr)(int &x, int &y);//声明一个函数指针 methodPtr = &swap; //函数指针赋值 methodPtr = swap;//取地址符可省略,效果和上面一致 methodPtr(x, y); //像给函数起了一个别名,可以直接使用()调用 cout << "x:" << x << " y:" << y << endl; //x:20 y:10 } void swap(int &x, int &y) { int tmp = x; x = y; y = tmp; }
回到Kotlin,我們也可以將一個函數傳遞給另一個函數,例如:
//函数参数 fun doMap(list: List, function: (it: T) -> Any) { for (item in list) { function(item) } }
第一個參數是一個List,第二個參數是一個函數,目的就是要將List中的每個元素執行一次第二個函數。使用方法如下:
val strList = listOf("h" ,"e", "1", "a", "b", "2", " ", "", "c", "5", "7", "F") doMap(strList, {item ->print("item: ${upperLetter(item)}, ") }) fun upperLetter(item: String): String { if (item.isLetter()) { return item.toUpperCase() } return item }
第二個參數直接傳進去了一個lambda表達式,當然也可以傳一個函數參考:
val strList = listOf("h" ,"e", "1", "a", "b", "2", " ", "", "c", "5", "7", "F") doMap(strList, ::printUpperLetter) fun printUpperLetter(item: String) { print("item: ${upperLetter(item)}, ") } fun upperLetter(item: String): String { if (item.isLetter()) { return item.toUpperCase() } return item }
效果和上面的程式碼一樣。
在C 中使用函數指標可以達到類似的效果:
using namespace std; void mMap(vector list, void (*fun)(int item)); int main(int arg, char* args[]) { vector list = {2,3,4,3,2,1,2}; mMap(list, [](int item) -> void { cout << item << endl; }); } void mMap(vector list, void (*fun)(int item)) { for(int it : list) { fun(it); } }
再回到Kotlin,如果函數作為入參在入參列表的最後一個,你還可以這樣做,直接寫在大括號內:
fun main(args: Array) { log { sum(1,2) } } fun log(function: () -> T) { val result = function() println("result -> $result") }
是不是有點像gradle設定檔的寫法,所以Kotlin可以很方便的寫 領域專用語言(DSL)
另外Kotlin也支援局部函數和函數作為回傳值,看下面的程式碼:
fun main(args: Array) { val addResult = lateAdd(2, 4) addResult() } //局部函数,函数引用 fun lateAdd(a: Int, b: Int): Function0 { fun add(): Int { return a + b } return ::add }
在lateAdd内部定义了一个局部函数,最后返回了该局部函数的引用,对结果使用()操作符拿到最终的结果,达到延迟计算的目的。
函数作为一级公民当然可以像普通对象一样放进map中,比如下面这样:
val funs = mapOf("sum" to ::sum) val mapFun = funs["sum"] if (mapFun != null) { val result = mapFun(1,2) println("sum result -> $result") } fun sum(a: Int, b: Int): Int { return a + b }
将一个函数引用作为value放进了map中,取出来之后使用()操作符调用,可以简化一些if,else的场景。
基于以上函数式编程的特性,Kotlin可以像RxJava一样很方便的进行相应式编程,比如:
fun printUpperLetter(list: List) { list .filter (fun(item):Boolean { return item.isNotEmpty() }) .filter { item -> item.isNotBlank()} .filter { item -> if (item.isNullOrEmpty()) { return@filter false } return@filter item.matches(Regex("^[a-z|A-Z]$")) } .filter { it.isLetter() } .map(String::toUpperCase) .sortedBy { it } .forEach { print("$it, ") } println() }
上面的代码只是做演示,并无实际意义。具体语法请查看官方文档。
我相信Kotlin作为一种强类型的现代化语言可以在保证稳定性的同时极大地提高开发者的开发效率。
以上是深入剖析Kotlin函數的使用方法與函數式程式設計原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!