第5章 函数与函数式编程
第5章 函數(shù)與函數(shù)式編程
凡此變數(shù)中函彼變數(shù)者,則此為彼之函數(shù)。 ( 李善蘭《代數(shù)學(xué)》)
函數(shù)式編程語言最重要的基礎(chǔ)是λ演算(lambda calculus),而且λ演算的函數(shù)可以傳入函數(shù)參數(shù),也可以返回一個(gè)函數(shù)。函數(shù)式編程 (簡稱FP) 是一種編程范式(programming paradigm)。
函數(shù)式編程與命令式編程最大的不同是:函數(shù)式編程的焦點(diǎn)在數(shù)據(jù)的映射,命令式編程(imperative programming)的焦點(diǎn)是解決問題的步驟。函數(shù)式編程不僅僅指的是Lisp、Haskell、 Scala等之類的語言,更重要的是一種編程思維,解決問題的思考方式,也稱面向函數(shù)編程。
函數(shù)式編程的本質(zhì)是函數(shù)的組合。例如,我們想要過濾出一個(gè)List中的奇數(shù),用Kotlin代碼可以這樣寫
package com.easy.kotlinfun main(args: Array<String>) {val list = listOf(1, 2, 3, 4, 5, 6, 7)println(list.filter { it % 2 == 1 }) // lambda表達(dá)式 }這個(gè)映射的過程可以使用下面的圖來形象化地說明
filter 函數(shù)而同樣的邏輯我們使用命令式的思維方式來寫的話,代碼如下
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;} }我們可以看出,函數(shù)式編程是簡單自然、直觀易懂且美麗優(yōu)雅的編程風(fēng)格。函數(shù)式編程語言中通常都會(huì)提供常用的map、reduce、filter等基本函數(shù),這些函數(shù)是對(duì)List、Map集合等基本數(shù)據(jù)結(jié)構(gòu)的常用操作的高層次封裝,就像一個(gè)更加智能好用的工具箱。
5.1 函數(shù)式編程簡介
函數(shù)式編程是關(guān)于不變性和函數(shù)組合的編程范式。函數(shù)式編程有如下特征
- 一等函數(shù)支持(first-class function):函數(shù)也是一種數(shù)據(jù)類型,可以當(dāng)做參數(shù)傳入另一個(gè)函數(shù),同時(shí)一個(gè)函數(shù)也可以返回函數(shù)。
- 純函數(shù)(pure function)和不變性(immutable):純函數(shù)指的是沒有副作用的函數(shù)(函數(shù)不去改變外部的數(shù)據(jù)狀態(tài))。例如,一個(gè)編譯器就是一個(gè)廣義上的純函數(shù)。在函數(shù)式編程中,傾向于使用純函數(shù)編程。正因?yàn)榧兒瘮?shù)不會(huì)去修改數(shù)據(jù),同時(shí)又使用不可變數(shù)據(jù),所以程序不會(huì)去修改一個(gè)已經(jīng)存在的數(shù)據(jù)結(jié)構(gòu),而是根據(jù)一定的映射邏輯創(chuàng)建一份新的數(shù)據(jù)。函數(shù)式編程是去轉(zhuǎn)換數(shù)據(jù)而非修改原始數(shù)據(jù)。
- 函數(shù)的組合(compose function):在面向?qū)ο缶幊讨?#xff0c;是通過對(duì)象之間發(fā)送消息來構(gòu)建程序邏輯;而在函數(shù)式編程中,是通過不同函數(shù)的組合構(gòu)建程序邏輯。
5.2 聲明函數(shù)
Kotlin中使用 fun 關(guān)鍵字來聲明函數(shù),其語法實(shí)例如下圖所示
Kotlin 聲明函數(shù)為了更加直觀的感受到函數(shù)也可以當(dāng)做變量來使用,我們聲明一個(gè)函數(shù)類型的變量 sum 如下
>>> val sum = fun(x:Int, y:Int):Int { return x + y } >>> sum (kotlin.Int, kotlin.Int) -> kotlin.Int我們可以看到這個(gè)函數(shù)變量 sum 的類型是
(kotlin.Int, kotlin.Int) -> kotlin.Int這個(gè)帶箭頭( -> )的表達(dá)式就是一個(gè)函數(shù)類型,表示一個(gè)輸入兩個(gè)Int類型值,輸出一個(gè)Int類型值的函數(shù)。我們可以直接使用這個(gè)函數(shù)字面值 sum
>>> sum(1,1) 2從上面的這個(gè)典型的例子我們可以看出,Kotlin也是一門面向表達(dá)式的語言。既然 sum 是一個(gè)代表函數(shù)類型的變量,稍后我們將看到一個(gè)函數(shù)可以當(dāng)做參數(shù)傳入另一個(gè)函數(shù)中(高階函數(shù))。
當(dāng)然,我們?nèi)匀豢梢韵馛/C++/Java中一樣,直接帶上函數(shù)名來聲明一個(gè)函數(shù)
fun multiply(x: Int, y: Int): Int {return x * y }multiply(2, 2) // 45.3 lambda表達(dá)式
我們?cè)诒菊麻_頭部分講到了這段代碼
val list = listOf(1, 2, 3, 4, 5, 6, 7) list.filter { it % 2 == 1 }這里的filter函數(shù)的入?yún)?{ it % 2 == 1 } 就是一段 lambda表達(dá)式。實(shí)際上,因?yàn)閒ilter函數(shù)只有一個(gè)參數(shù),所有括號(hào)被省略了。所以,filter函數(shù)調(diào)用的完整寫法是
list.filter ({ it % 2 == 1 })其中的filter函數(shù)聲明如下
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T>其實(shí),filter函數(shù)的入?yún)⑹且粋€(gè)函數(shù) predicate: (T) -> Boolean 。 實(shí)際上,
{ it % 2 == 1 }是一種簡寫的語法,完整的lambda表達(dá)式是這樣寫的
{ it -> it % 2 == 1 }如果拆開來寫,就更加容易理解
>>> val isOdd = { it: Int -> it % 2 == 1 } // 直接使用lambda表達(dá)式聲明一個(gè)函數(shù),這個(gè)函數(shù)判斷輸入的Int是不是奇數(shù) >>> isOdd (kotlin.Int) -> kotlin.Boolean // isOdd函數(shù)的類型 >>> val list = listOf(1, 2, 3, 4, 5, 6, 7) >>> list.filter(isOdd) // 直接傳入isOdd函數(shù) [1, 3, 5, 7]5.4 高階函數(shù)
其實(shí),在上面的代碼示例 list.filter(isOdd) 中,我們已經(jīng)看到了高階函數(shù)了。現(xiàn)在我們?cè)偬砑右粚佑成溥壿嫛N覀冇幸粋€(gè)字符串列表
val strList = listOf("a", "ab", "abc", "abcd", "abcde", "abcdef", "abcdefg")然后,我們想要過濾出字符串元素的長度是奇數(shù)的列表。我們把這個(gè)問題的解決邏輯拆成兩個(gè)函數(shù)來組合實(shí)現(xiàn)
val f = fun (x: Int) = x % 2 == 1 // 判斷輸入的Int是否奇數(shù) val g = fun (s: String) = s.length // 返回輸入的字符串參數(shù)的長度我們?cè)偈褂煤瘮?shù) h 來封裝 “字符串元素的長度是奇數(shù)” 這個(gè)邏輯,實(shí)現(xiàn)代碼如下
val h = fun(g: (String) -> Int, f: (Int) -> Boolean): (String) -> Boolean {return { f(g(it)) } }但是,這個(gè) h 函數(shù)的聲明未免有點(diǎn)太長了。尤其是3個(gè)函數(shù)類型聲明的箭頭表達(dá)式,顯得不夠簡潔。不過不用擔(dān)心。
Kotlin中有簡單好用的 Kotlin 類型別名, 我們使用 G,F,H 來聲明3個(gè)函數(shù)類型
typealias G = (String) -> Int typealias F = (Int) -> Boolean typealias H = (String) -> Boolean那么,我們的 h 函數(shù)就可簡單優(yōu)雅的寫成下面這樣了
val h = fun(g: G, f: F): H {return { f(g(it)) } // 需要注意的是,這里的 {} 是不能省略的 }這個(gè) h 函數(shù)的映射關(guān)系可用下圖說明
h 函數(shù)的映射關(guān)系函數(shù)體中的這句代碼 return { f(g(it)) } , 這里的 {} 它代表這是一個(gè)lambda表達(dá)式,返回的是一個(gè) (String) -> Boolean 函數(shù)類型。如果沒有 { } , 那么返回值就是一個(gè)布爾類型Boolean了。
通過上面的代碼例子,我們可以看到,在Kotlin中,我們可以簡單優(yōu)雅的實(shí)現(xiàn)高階函數(shù)。OK,現(xiàn)在邏輯已經(jīng)實(shí)現(xiàn)完了,下面我們?cè)?main 函數(shù)中運(yùn)行測試一下效果。
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] }當(dāng)你看到 h(g, f) 這樣的復(fù)合函數(shù)的代碼時(shí),你一定很開心,感到很自然,這跟數(shù)學(xué)公式真是很貼近,簡單易懂。
本章小結(jié)
在Kotlin中,支持函數(shù)作為一等公民。它支持高階函數(shù)、lambda表達(dá)式等。我們不僅可以把函數(shù)當(dāng)做普通變量一樣傳遞、返回,還可以把它分配給變量、放進(jìn)數(shù)據(jù)結(jié)構(gòu)或者進(jìn)行一般性的操作。在Kotlin中進(jìn)行函數(shù)式編程相當(dāng)簡單自如。
總結(jié)
以上是生活随笔為你收集整理的第5章 函数与函数式编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Bootstrap学习笔记系列1----
- 下一篇: Oracle-Decode()函数和CA