《Kotlin項目實戰開發》第5章 函數與函數式編程

第5章 函數與函數式編程java

凡此變數中函彼變數者,則此爲彼之函數。 ( 李善蘭《代數學》)編程

函數式編程語言最重要的基礎是λ演算(lambda calculus),並且λ演算的函數能夠傳入函數參數,也能夠返回一個函數。函數式編程 (簡稱FP) 是一種編程範式(programming paradigm)。數組

函數式編程與命令式編程最大的不一樣是:函數式編程的焦點在數據的映射,命令式編程(imperative programming)的焦點是解決問題的步驟。函數式編程不單單指的是Lisp、Haskell、 Scala等之類的語言,更重要的是一種編程思惟,解決問題的思考方式,也稱面向函數編程。數據結構

函數式編程的本質是函數的組合。例如,咱們想要過濾出一個List中的奇數,用Kotlin代碼能夠這樣寫編程語言

package com.easy.kotlin

fun main(args: Array<String>) {
    val list = listOf(1, 2, 3, 4, 5, 6, 7)
    println(list.filter { it % 2 == 1 }) // lambda表達式
}

這個映射的過程可使用下面的圖來形象化地說明函數式編程

filter 函數

而一樣的邏輯咱們使用命令式的思惟方式來寫的話,代碼以下函數

package com.easy.kotlin;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static java.lang.System.out;

public class FilterOddsDemo {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(new Integer[] {1, 2, 3, 4, 5, 6, 7});
        out.println(filterOdds(list)); // 輸出:[1, 3, 5, 7]
    }

    public static List<Integer> filterOdds(List<Integer> list) {
        List<Integer> result = new ArrayList();
        for (Integer i : list) {
            if (isOdd(i)) {
                result.add(i);
            }
        }
        return result;
    }

    private static boolean isOdd(Integer i) {
        return i % 2 != 0;
    }
}

咱們能夠看出,函數式編程是簡單天然、直觀易懂且美麗優雅的編程風格。函數式編程語言中一般都會提供經常使用的map、reduce、filter等基本函數,這些函數是對List、Map集合等基本數據結構的經常使用操做的高層次封裝,就像一個更加智能好用的工具箱。工具

5.1 函數式編程簡介

函數式編程是關於不變性和函數組合的編程範式。函數式編程有以下特徵測試

  • 一等函數支持(first-class function):函數也是一種數據類型,能夠當作參數傳入另外一個函數,同時一個函數也能夠返回函數。
  • 純函數(pure function)和不變性(immutable):純函數指的是沒有反作用的函數(函數不去改變外部的數據狀態)。例如,一個編譯器就是一個廣義上的純函數。在函數式編程中,傾向於使用純函數編程。正由於純函數不會去修改數據,同時又使用不可變數據,因此程序不會去修改一個已經存在的數據結構,而是根據必定的映射邏輯建立一份新的數據。函數式編程是去轉換數據而非修改原始數據。
  • 函數的組合(compose function):在面向對象編程中,是經過對象之間發送消息來構建程序邏輯;而在函數式編程中,是經過不一樣函數的組合構建程序邏輯。

5.2 聲明函數

Kotlin中使用 fun 關鍵字來聲明函數,其語法實例以下圖所示spa

Kotlin 聲明函數

爲了更加直觀的感覺到函數也能夠當作變量來使用,咱們聲明一個函數類型的變量 sum 以下

>>> val sum = fun(x:Int, y:Int):Int { return x + y }
>>> sum
(kotlin.Int, kotlin.Int) -> kotlin.Int

咱們能夠看到這個函數變量 sum 的類型是

(kotlin.Int, kotlin.Int) -> kotlin.Int

這個帶箭頭( -> )的表達式就是一個函數類型,表示一個輸入兩個Int類型值,輸出一個Int類型值的函數。咱們能夠直接使用這個函數字面值 sum

>>> sum(1,1)
2

從上面的這個典型的例子咱們能夠看出,Kotlin也是一門面向表達式的語言。既然 sum 是一個表明函數類型的變量,稍後咱們將看到一個函數能夠當作參數傳入另外一個函數中(高階函數)。

固然,咱們仍然能夠像C/C++/Java中同樣,直接帶上函數名來聲明一個函數

fun multiply(x: Int, y: Int): Int {
    return x * y
}

multiply(2, 2) // 4

5.3 lambda表達式

咱們在本章開頭部分講到了這段代碼

val list = listOf(1, 2, 3, 4, 5, 6, 7)
list.filter { it % 2 == 1 }

這裏的filter函數的入參 { it % 2 == 1 } 就是一段 lambda表達式。實際上,由於filter函數只有一個參數,全部括號被省略了。因此,filter函數調用的完整寫法是

list.filter ({ it % 2 == 1 })

其中的filter函數聲明以下

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T>

其實,filter函數的入參是一個函數 predicate: (T) -> Boolean 。 實際上,

{ it % 2 == 1 }

是一種簡寫的語法,完整的lambda表達式是這樣寫的

{  it -> it % 2 == 1 }

若是拆開來寫,就更加容易理解

>>> val isOdd = { it: Int -> it % 2 == 1 } // 直接使用lambda表達式聲明一個函數,這個函數判斷輸入的Int是否是奇數
>>> isOdd
(kotlin.Int) -> kotlin.Boolean // isOdd函數的類型
>>> val list = listOf(1, 2, 3, 4, 5, 6, 7)
>>> list.filter(isOdd) // 直接傳入isOdd函數
[1, 3, 5, 7]

5.4 高階函數

其實,在上面的代碼示例 list.filter(isOdd) 中,咱們已經看到了高階函數了。如今咱們再添加一層映射邏輯。咱們有一個字符串列表

val strList = listOf("a", "ab", "abc", "abcd", "abcde", "abcdef", "abcdefg")

而後,咱們想要過濾出字符串元素的長度是奇數的列表。咱們把這個問題的解決邏輯拆成兩個函數來組合實現

val f = fun (x: Int) = x % 2 == 1 // 判斷輸入的Int是否奇數
val g = fun (s: String) = s.length // 返回輸入的字符串參數的長度

咱們再使用函數 h 來封裝 「字符串元素的長度是奇數」 這個邏輯,實現代碼以下

val h = fun(g:  (String) -> Int, f: (Int) -> Boolean):  (String) -> Boolean {
    return { f(g(it)) }
}

可是,這個 h 函數的聲明未免有點太長了。尤爲是3個函數類型聲明的箭頭表達式,顯得不夠簡潔。不過不用擔憂。

Kotlin中有簡單好用的 Kotlin 類型別名, 咱們使用 G,F,H 來聲明3個函數類型

typealias G = (String) -> Int
typealias F = (Int) -> Boolean
typealias H = (String) -> Boolean

那麼,咱們的 h 函數就可簡單優雅的寫成下面這樣了

val h = fun(g: G, f: F): H {
    return { f(g(it)) } // 須要注意的是,這裏的 {} 是不能省略的
}

這個 h 函數的映射關係可用下圖說明

h 函數的映射關係

函數體中的這句代碼 return { f(g(it)) } , 這裏的 {} 它表明這是一個lambda表達式,返回的是一個 (String) -> Boolean 函數類型。若是沒有 { } , 那麼返回值就是一個布爾類型Boolean了。

經過上面的代碼例子,咱們能夠看到,在Kotlin中,咱們能夠簡單優雅的實現高階函數。OK,如今邏輯已經實現完了,下面咱們在 main 函數中運行測試一下效果。

fun main(args: Array<String>) {
    val strList = listOf("a", "ab", "abc", "abcd", "abcde", "abcdef", "abcdefg")
    println(strList.filter(h(g, f))) // 輸出:[a, abc, abcde, abcdefg]
}

當你看到 h(g, f) 這樣的複合函數的代碼時,你必定很開心,感到很天然,這跟數學公式真是很貼近,簡單易懂。

本章小結

相關文章
相關標籤/搜索