摘要:尾聲除了以上特性,函數(shù)式編程中還有,等比較難以理解的概念,本文暫時不牽扯那么深,留待有興趣的人自行調(diào)查。
本文簡單介紹了一下函數(shù)式編程的各種基本特性,希望能夠?qū)τ跍?zhǔn)備使用函數(shù)式編程的人起到一定入門作用。
函數(shù)式編程,一個一直以來都酷,很酷,非??岬拿~。雖然誕生很早也炒了很多年但是一直都沒有造成很大的水花,不過近幾年來隨著多核,分布式,大數(shù)據(jù)的發(fā)展,函數(shù)式編程已經(jīng)廣泛投入到了實戰(zhàn)中。
然而現(xiàn)實中還是有不少人不太了解這種編程范式,覺得這僅僅是一個逼格較高的名詞。我們這里就來簡單介紹一下這個舉手投足都充滿酷勁的小東西。
本文之后的代碼主要以 Java 和 Scala 為主,前者說明如何在非函數(shù)式語言中實現(xiàn)函數(shù)式風(fēng)格,后者說明在函數(shù)式語言中是如何做的。代碼比較簡單,無論你是否懂這兩門語言,相信都能很容易看懂。此外由于函數(shù)式編程這幾個詞太長了,以下都以 FP 進(jìn)行簡寫。
特性 函數(shù)是一等公民所謂的函數(shù)是一等公民指的是在 FP 中,函數(shù)可以作為直接作為變量的值。
例
Scala
val add = (x: Int, y: Int) => x + y add(1, 2)
以上我們定義了一個負(fù)責(zé)將兩個整型相加的匿名函數(shù)并賦值給變量 add,并且直接將這個變量當(dāng)前函數(shù)進(jìn)行調(diào)用,這在大部分面向?qū)ο蟮恼Z言中你都是無法直接這樣做的。
Java
interface Adder { int add(int x, int y); } Adder adder = (x, y) -> x + y; adder.add(1, 2);
由于 Java 并不是函數(shù)式語言,所以無法直接將函數(shù)賦值給變量,因此以上例子中我們使用 SAM 轉(zhuǎn)換來實現(xiàn)近似功能。
閉包閉包是一種帶有自由變量的代碼塊,其最根本的功能就是能夠擴(kuò)大局部變量的生命周期。閉包相信很多人都很熟悉,在 JavaScript 中閉包無處不在,是一種很好用但是一不注意就會掉坑里的特性。
例
Scala
var factor = 10 factor = factor * 10 val multiplier = (x: Int) => x * factor
以上例子中函數(shù)體使用了兩個參數(shù),其中 x 只是很普通的函數(shù)參數(shù),而 factor 則是函數(shù)體外定義的一個局部變量,且該變量可以任意進(jìn)行修改,所以對 factor 的引用使該函數(shù)變成了一個閉包。
Java
int factor = 10; // factor = factor * 10; Multiplier multiplier = (x) -> x * factor;
在 Java 中匿名函數(shù)只能引用外部的 final 變量,Java 8 雖然可以省略 final 關(guān)鍵字,但是實際還是沒有任何變化,所以第二句語句必須注釋掉。這也就是說在 Java 中實際是無法使用自由變量的,因此 Java 是否有真正的閉包一直都是一個爭論點,這里就不多牽扯了。
惰性求值 Lazy Evaluation一般而言成員變量在實例創(chuàng)建時就會被初始化,而惰性求值可以將初始化的過程延遲到變量的第一次使用,對于成員變量的值需要經(jīng)過大量計算的類來說可以有效加快實例的創(chuàng)建過程。
例
Scala
lazy val lazyField = { var sum = 0 for (i <- 1 to 100) { sum += i } sum }
在 Scala 中是通過關(guān)鍵字 lazy 來聲明惰性求值的。在以上例子中定義了一個從 1 加到 100 的惰性變量,在第一次訪問該變量時這個計算過程才會被執(zhí)行。
Java
SupplierlazyField = () -> { int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } return sum; };
Java 雖然在語言層面沒有提供該功能,但是可以通過 Java 8 提供的 Supplier 接口來實現(xiàn)同樣的功能。
尾遞歸 Tail Recursion遞歸大家都知道,就是函數(shù)自己調(diào)用自己。
例
定義一個遞歸函數(shù)
def addOne(i: Int) { if (i > 3) return println(s"before $i") addOne(i + 1) println(s"after $i") }
調(diào)用以上函數(shù)并傳入?yún)?shù) 3 會打印如下語句
before 1 before 2 before 3 after 3 after 2 after 1
這就是遞歸的基本形式。在每次遞歸調(diào)用時程序都必須保存當(dāng)前的方法調(diào)用棧,即調(diào)用 addOne(2) 時程序必須記住之前是如何調(diào)用 addOne(1) 的,這樣它才能在執(zhí)行完 addOne(2) 后返回到 addOne(1) 的下一條語句并打印 after 1。因此在 Java 等語言中遞歸一來影響效率,二來消耗內(nèi)存,調(diào)用次數(shù)過多時會引起方法棧溢出。
而尾遞歸指的就是只在函數(shù)的最后一個語句調(diào)用遞歸。這樣的好處是可以使用很多 FP 語言都支持的尾遞歸優(yōu)化或者叫尾遞歸消除,即遞歸調(diào)用時直接將函數(shù)的調(diào)用者傳入到下一個遞歸函數(shù)中,并將當(dāng)前函數(shù)彈出棧中,在最后一次遞歸調(diào)用完畢后直接返回傳入的調(diào)用者處而不是返回上一次遞歸的調(diào)用處。
用簡單的示意圖即是由原來的
line xxx, call addOne -> addOne(1) -> addOne(2) -> addOne(3) -> addOne(2) -> addOne(1) -> line xxx
優(yōu)化為
line xxx, call addOne -> addOne(1) -> addOne(2) -> addOne(3) -> line xxx純函數(shù) Pure Function
純函數(shù)并不是 FP 的特性,而是 FP 中一些特性的集合。所謂的純函數(shù)簡單來講就是函數(shù)不能有副作用,保證引用透明。即函數(shù)本身不會修改參數(shù)的值也不會修改函數(shù)外的變量,無論執(zhí)行多少次,同樣的輸入都會有同樣的輸出。
例
定義三個函數(shù)
def add(x: Int, y: Int) = x + y def clear(list: mutable.MutableList): Unit = { list.clear() } def random() = Random.nextInt()
以上代碼中定義了三個函數(shù),其中 add() 符合純函數(shù)的定義;clear() 會清除傳入的 List 的所有元素,所以不是純函數(shù);random() 無法保證每次調(diào)用都產(chǎn)生同樣的輸入,所以也不是純函數(shù)。
高階函數(shù) High-Order Function高階函數(shù)指一個函數(shù)的參數(shù)是另一個函數(shù),或者一個函數(shù)的返回值是另一個函數(shù)。
例
參數(shù)為函數(shù)
def assert(predicate: () => Boolean) = if (!predicate()) throw new RuntimeException("assert failed") assert(() => 1 == 2)
以上函數(shù) assert() 接收一個匿名函數(shù) () => 1 == 2 作為參數(shù),本質(zhì)上是應(yīng)用了傳名調(diào)用的特性。
返回值為函數(shù)
def create(): Int => Int = { val factor = 10 (x: Int) => x * factor }集合操作 Collection
集合操作可以說是 FP 中最常用的一個特性,激進(jìn)的 FP 擁護(hù)者甚至認(rèn)為應(yīng)該使用 foreach 替代所有循環(huán)語句。這些集合操作本質(zhì)上就是多個內(nèi)置的高階函數(shù)。
例
Scala
val list = List(1, 2, 3) list.map(i => { println(s"before $i") i * 2 }).map(i => i + 1) .foreach(i => println(s"after $i"))
以上定義了一個包含三個整形的列表,依次對其中每個元素乘以 2 后再加 1,最后進(jìn)行打印操作。輸出結(jié)果如下:
before 1 before 2 before 3 after 3 after 5 after 7
可以看到 FP 中的集合操作關(guān)注的是數(shù)據(jù)本身,至于如何遍歷數(shù)據(jù)這一行為則是交給了語言內(nèi)部機(jī)制來實現(xiàn)。相比較 for 循環(huán)來說這有兩個比較明顯的優(yōu)點:1. 一定程度上防止了原數(shù)據(jù)被修改,2. 不用關(guān)心遍歷的順序。這樣用戶可以在必要時將操作放到多線程中而不用擔(dān)心引起一些副作用,編譯器也可以在編譯時自行對遍歷進(jìn)行深度優(yōu)化。
Java
Listlist = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.stream() .map(i -> { System.out.println("before " + i); return i * 2; }).map(i -> i + 1) .forEach(i -> System.out.println("after " + i));
輸出
before 1 after 3 before 2 after 5 before 3 after 7
可以從以上輸出看到對于集合操作 Scala 和 Java 的實現(xiàn)完全不一樣。
Scala 中一個操作中的所有數(shù)據(jù)完成處理后才流向下一個操作,可以看做每個操作都是一個關(guān)卡。而 Java 則是默認(rèn)使用了惰性求值的方式,并且概念非常類似 Spark。其各種集合操作主要分為兩種: transformation 和 action。transformation 即轉(zhuǎn)換操作,所有返回 Stream 對象的函數(shù)都是 transformation 操作,該操作不會立即執(zhí)行,而是將執(zhí)行步驟保存在 Stream 對象中。action 即執(zhí)行操作,action 沒有返回值,調(diào)用后會立即執(zhí)行之前 Stream 對象中保存的所有操作。 map() 這樣的就是 transformation 操作,forEach() 就是 action 操作。
柯理化 Currying柯里化指的是將一個接收多個參數(shù)的函數(shù)分解成多個接收單個參數(shù)的函數(shù)的一種技術(shù)。
比如說有這樣一個普通的函數(shù)
def minus(x: Int, y: Int) = x - y
柯理化后就變成以下形式,一個減法操作被分割為兩部分
def minusCurrying(x: Int)(y: Int) = x - y
調(diào)用以上兩個函數(shù)
minus(5, 3) minusCurrying(5)(3)部分應(yīng)用 Function Partial Application
函數(shù)的部分應(yīng)用指的是向一個接收多個參數(shù)的函數(shù)傳入部分參數(shù)從而獲得一個接收剩余參數(shù)的新函數(shù)的技術(shù)。
比如說有這樣一個包含多個參數(shù)的函數(shù)
def show(prefix: String, msg: String, postfix: String) = prefix + msg + postfix
獲得部分應(yīng)用函數(shù)
val applyPrefix = show("(", _: String, _: String) println(applyPrefix("foo", ")")) // (foo) val applyPostfix = show(_: String, _: String, ")") println(applyPostfix("(", "bar")) // (bar)
以上?applyPrefix()?是應(yīng)用了?show()?的第一個參數(shù)的新函數(shù),applyPostfix()?是應(yīng)用了?show()?的最后一個參數(shù)的新函數(shù)。
偏函數(shù) Partial Function函數(shù)指對于所有給定類型的輸入,總是存在特定類型的輸出。
偏函數(shù)指對于某些給定類型的輸入,可能沒有對應(yīng)的輸出,即偏函數(shù)無法處理給定類型范圍內(nèi)的所有值。
定義一個偏函數(shù)
val isEven: PartialFunction[Int, String] = { case x if x != 0 && x % 2 == 0 => x + " is even" }
以上 isEven() 只能處理偶數(shù),對于奇數(shù)則無法處理,所以是一個偏函數(shù)。
偏函數(shù)可以用于責(zé)任鏈模式,每個偏函數(shù)只處理部分類型的數(shù)據(jù),其余類型的數(shù)據(jù)由下一個偏函數(shù)進(jìn)行處理。
val isOdd: PartialFunction[Int, String] = { case x if x % 2 != 0 => x + " is odd" } val other: PartialFunction[Int, String] = { case _ => "else" } val partial = isEven orElse isOdd orElse other println(partial(3)) // 3 is odd println(partial(0)) // else尾聲
除了以上特性,函數(shù)式編程中還有 Monoid,SemiGroup 等比較難以理解的概念,本文暫時不牽扯那么深,留待有興趣的人自行調(diào)查。最后我想說的是使用函數(shù)式編程的確很坂本,但是多了解一種編程范式對于從碼農(nóng)進(jìn)化為碼農(nóng)++還是很有幫助的。
如果你對以上代碼有興趣的話可以直接訪問 https://github.com/SidneyXu/JGSK。
作者信息
作者來自力譜宿云 LeapCloud 團(tuán)隊_UX成員:Sidney Xu【原創(chuàng)】
首發(fā)地址:https://blog.maxleap.cn/archives/964
簡介:多年后端及移動端開發(fā)經(jīng)驗,現(xiàn)任 力譜宿云LeapCloud UX 團(tuán)隊成員,主要從事于 Android 相關(guān)開發(fā),目前對 Kotlin 和 Ruby 有濃厚興趣。
近期線下技術(shù)活動預(yù)告:
活動主題:【技術(shù)分享】Vert.x中國用戶組(上海地區(qū))第一次技術(shù)沙龍
分享內(nèi)容:
分享主題1:JVM上的高性能Reative工具Vert.x3介紹
分享主題1:Vert.x在maxleap的最佳實踐
分享主題1:Vert-web注解封裝
活動時間:2016/07/24(周日) 14:00 至 2016/07/24 17:00 ?
活動場地:上海浦東新區(qū)金科路2889弄(近祖沖之路)長泰廣場C座12層
報名鏈接:http://www.hdb.com/party/ydtru-comm.html
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/64902.html
摘要:函數(shù)式編程,一看這個詞,簡直就是學(xué)院派的典范。所以這期周刊,我們就重點引入的函數(shù)式編程,淺入淺出,一窺函數(shù)式編程的思想,可能讓你對編程語言的理解更加融會貫通一些。但從根本上來說,函數(shù)式編程就是關(guān)于如使用通用的可復(fù)用函數(shù)進(jìn)行組合編程。 showImg(https://segmentfault.com/img/bVGQuc); 函數(shù)式編程(Functional Programming),一...
摘要:首先,在學(xué)習(xí)之前一定會考慮一個問題版本選擇對于編程零基礎(chǔ)的人來說,選擇。建議從下面課程開始教程標(biāo)準(zhǔn)庫官方文檔非常貼心地提供中文翻譯首先需要學(xué)習(xí)的基礎(chǔ)知識,下載安裝導(dǎo)入庫字符串處理函數(shù)使用等等。 提前說一下,這篇福利多多,別的不說,直接讓你玩回最有手感的懷舊游戲,參數(shù)貼圖很方便自己可以根據(jù)喜好修改哦。 本篇通過以下四塊展開,提供大量資源對應(yīng)。 showImg(https://segmen...
摘要:避免脆弱的基類問題。紅牌警告沒有提到上述任何問題。單向數(shù)據(jù)流意味著模型是單一的事實來源。單向數(shù)據(jù)流是確定性的,而雙向綁定可能導(dǎo)致更難以遵循和理解的副作用。原文地址 1. 你能說出兩種對 JavaScript 應(yīng)用開發(fā)者而言的編程范式嗎? 希望聽到: 2. 什么是函數(shù)編程? 希望聽到: 3. 類繼承和原型繼承的不同? 希望聽到 4. 函數(shù)式編程和面向?qū)ο缶幊痰膬?yōu)缺點? ...
摘要:前端進(jìn)階進(jìn)階構(gòu)建項目一配置最佳實踐狀態(tài)管理之痛點分析與改良開發(fā)中所謂狀態(tài)淺析從時間旅行的烏托邦,看狀態(tài)管理的設(shè)計誤區(qū)使用更好地處理數(shù)據(jù)愛彼迎房源詳情頁中的性能優(yōu)化從零開始,在中構(gòu)建時間旅行式調(diào)試用輕松管理復(fù)雜狀態(tài)如何把業(yè)務(wù)邏輯這個故事講好和 前端進(jìn)階 webpack webpack進(jìn)階構(gòu)建項目(一) Webpack 4 配置最佳實踐 react Redux狀態(tài)管理之痛點、分析與...
摘要:函數(shù)式編程的準(zhǔn)則不依賴于外部的數(shù)據(jù),而且也不改變外部數(shù)據(jù)的值,而是返回一個新的值給你。函數(shù)式編程利用純函數(shù)的無狀態(tài)性,它的好處非常多結(jié)果可預(yù)期利于測試?yán)趶?fù)用利于并發(fā),但一個系統(tǒng)工程的代碼,是不可能全部采用純函數(shù)來寫的。 什么是函數(shù)式編程 函數(shù)式編程是一種編程范式,常見的編程范式有以下三種: 命令式編程 聲明式編程 函數(shù)式編程 函數(shù)式編程的本質(zhì)是將計算描述為一種表達(dá)式求值。在函數(shù)式...
閱讀 2597·2023-04-25 17:37
閱讀 1262·2021-11-24 10:29
閱讀 3790·2021-09-09 11:57
閱讀 771·2021-08-10 09:41
閱讀 2303·2019-08-30 15:55
閱讀 2874·2019-08-30 15:54
閱讀 2015·2019-08-30 15:53
閱讀 965·2019-08-30 15:43