解析Kotlin 函數用法與函數式編程

導讀 本篇文章主要介紹Kotlin函數的用法,以及本身對函數式編程的一些理解。而且會和Python,C++作一些比較。

自從Google爸爸宣佈Kotlin爲本身的乾兒子以後,Kotlin被各大社區炒的火熱。
若是你對Kotlin語法一無所知,推薦先閱讀官方文檔或者中文站(https://www.kotlincn.net/docs/reference/)以後再看這篇文章會有更深入的理解。javascript

下面是維基百科上對於函數式編程的定義:html

函數式編程(英語:functional programming)或稱函數程序設計,又稱泛函編程,是一種編程範型,它將電腦運算視爲數學上的函數計算,而且避免使用程序狀態以及易變對象。函數編程語言最重要的基礎是λ演算(lambda calculus)。並且λ演算的函數能夠接受函數看成輸入(引數)和輸出(傳出值)。java

下面是關於高階函數的定義:linux

在數學和計算機科學中,高階函數是至少知足下列一個條件的函數:接受一個或多個函數做爲輸入,輸出一個函數android

不難推斷出函數式編程最重要的基礎是高階函數。也就是支持函數能夠接受函數看成輸入(引數)和輸出(傳出值)。c++

函數做爲Kotlin中的一級公民能夠像其餘對象同樣做爲函數的輸入與輸出,這也就是Java程序員轉到Kotlin以爲變化最大,最難理解的一點。若是你以前學過Python或者C++11可能會對此比較容易接受。這也是爲何本文以介紹Kotlin的函數及函數式編程爲主。程序員

Kotlin 函數express

下面是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 表達式」(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做爲一種強類型的現代化語言能夠在保證穩定性的同時極大地提升開發者的開發效率。

原文來自:http://www.linuxprobe.com/kotlin-function-analysis.html

相關文章
相關標籤/搜索