摘要:即使你有一個多線程程序,大多數(shù)線程都被阻塞等待完成,例如文件,網(wǎng)絡(luò)等等。但是只要能夠提升我們程序的效率,要付出努力來寫好多線程程序這是值得的。然而,多線程有兩個主要問題多線程程序難于編寫讀取解釋測試和調(diào)試。
想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你!
本系列的其它篇:
學(xué)會使用函數(shù)式編程的程序員(第1部分)
學(xué)會使用函數(shù)式編程的程序員(第2部分)
引用透明 (Referential Transparency)引用透明是一個富有想象力的優(yōu)秀術(shù)語,它是用來描述純函數(shù)可以被它的表達式安全的替換,通過下例來幫助我們理解。
在代數(shù)中,有一個如下的公式:
y = x + 10
接著:
x = 3
然后帶入表達式:
y = 3 + 10
注意這個方程仍然是有效的,我們可以利用純函數(shù)做一些相同類型的替換。
下面是一個 JavaScript 的方法,在傳入的字符串兩邊加上單引號:
function quote (str) { retrun """ + str + """ }
下面是調(diào)用它:
function findError (key) { return "不能找到 " + quote(key) }
當查詢 key 值失敗時,findError 返回一個報錯信息。
因為 quote 是純函數(shù),我們可以簡單地將 quote 函數(shù)體(這里僅僅只是個表達式)替換掉在findError中的方法調(diào)用:
function findError (key) { return "不能找到 " + """ + str + """ }
這個就是通常所說的“反向重構(gòu)”(它對我而言有更多的意義),可以用來幫程序員或者程序(例如編譯器和測試程序)推理代碼的過程一個很好的方法。如,這在推導(dǎo)遞歸函數(shù)時尤其有用的。
執(zhí)行順序 (Execution Order)大多數(shù)程序都是單線程的,即一次只執(zhí)行一段代碼。即使你有一個多線程程序,大多數(shù)線程都被阻塞等待I/O完成,例如文件,網(wǎng)絡(luò)等等。
這也是當我們編寫代碼的時候,我們很自然考慮按次序來編寫代碼:
1. 拿到面包 2. 把2片面包放入烤面包機 3. 選擇加熱時間 4. 按下開始按鈕 5. 等待面包片彈出 6. 取出烤面包 7. 拿黃油 8. 拿黃油刀 9. 制作黃油面包
在這個例子中,有兩個獨立的操作:拿黃油以及 加熱面包。它們在 步驟9 時開始變得相互依賴。
我們可以將 步驟7 和 步驟8 與 步驟1 到 步驟6 同時執(zhí)行,因為它們彼此獨立。當我們開始做的時候,事情開始復(fù)雜了:
線程一 -------------------------- 1. 拿到面包 2. 把2片面包放入烤面包機 3. 選擇加熱時間 4. 按下開始按鈕 5. 等待面包片彈出 6. 取出烤面包 線程二 ------------------------- 1. 拿黃油 2. 拿黃油刀 3. 等待線程1完成 4. 取出烤面包
果線程1失敗,線程2怎么辦? 怎么協(xié)調(diào)這兩個線程? 烤面包這一步驟在哪個線程運行:線程1,線程2或者兩者?
不考慮這些復(fù)雜性,讓我們的程序保持單線程會更容易。但是,只要能夠提升我們程序的效率,要付出努力來寫好多線程程序,這是值得的。
然而,多線程有兩個主要問題:
多線程程序難于編寫、讀取、解釋、測試和調(diào)試。
一些語言,例如JavaScript,并不支持多線程,就算有些語言支持多線程,對它的支持也很弱。
但是,如果順序無關(guān)緊要,所有事情都是并行執(zhí)行的呢?
盡管這聽起來有些瘋狂,但其實并不像聽起來那么混亂。讓我們來看一下 Elm 的代碼來形象的理解它:
buildMessage message value = let upperMessage = String.toUpper message quotedValue = """ ++ value ++ """ in upperMessage ++ ": " ++ quotedValue
這里的 buildMessage 接受參數(shù) message 和 value,然后,生成大寫的 message和 帶有引號的 value 。
注意到 upperMessage 和 quotedValue 是獨立的。我們怎么知道的呢?
在上面的代碼示例中,upperMessage 和 quotedValue 兩者都是純的并且沒有一個需要依賴其它的輸出。
如果它們不純,我們就永遠不知道它們是獨立的。在這種情況下,我們必須依賴程序中調(diào)用它們的順序來確定它們的執(zhí)行順序。這就是所有命令式語言的工作方式。
第二點必須滿足的就是一個函數(shù)的輸出值不能作為其它函數(shù)的輸入值。如果存在這種情況,那么我們不得不等待其中一個完成才能執(zhí)行下一個。
在本例中,upperMessage 和 quotedValue 都是純的并且沒有一個需要依賴其它的輸出,因此,這兩個函數(shù)可以以任何順序執(zhí)行。
編譯器可以在不需要程序員幫助的情況下做出這個決定。這只有在純函數(shù)式語言中才有可能,因為很難(如果不是不可能的話)確定副作用的后果。
在純函數(shù)語言中,執(zhí)行的順序可以由編譯器決定。
考慮到 CPU 無法一再的加快速度,這種做法非常有利的。別一方面,生產(chǎn)商也不斷增加CPU內(nèi)核芯片的數(shù)量,這意味著代碼可以在硬件層面上并行執(zhí)行。使用純函數(shù)語言,就有希望在不改變?nèi)魏未a的情況下充分地發(fā)揮 CPU 芯片的功能并取得良好成效。
類型注釋 (Type Annotations)在靜態(tài)類型語言中,類型是內(nèi)聯(lián)定義的。以下是 Java 代碼:
public static String quote(String str) { return """ + str + """; }
注意類型是如何同函數(shù)定義內(nèi)聯(lián)在一起的。當有泛型時,它變的更糟:
private final MapgetPerson(Map people, Integer personId) { // ... }
這里使用粗體標出了使它們使用的類型,但它們?nèi)匀粫尯瘮?shù)可讀性降低,你必須仔細閱讀才能找到變量的名稱。
對于動態(tài)類型語言,這不是問題。在 Javascript 中,可以編寫如下代碼:
var getPerson = function(people, personId) { // ... };
這樣沒有任何的的類型信息更易于閱讀,唯一的問題就是放棄了類型檢測的安全特性。這樣能夠很簡單的傳入這些參數(shù),例如,一個 Number 類型的 people 以及一個 Objec t類型的 personId。
動態(tài)類型要等到程序執(zhí)行后才能知道哪里問題,這可能是在發(fā)布的幾個月后。在 Java 中不會出現(xiàn)這種情況,因為它不能被編譯。
但是,假如我們能同時擁有這兩者的優(yōu)異點呢? JavaScript 的語法簡單性以及 Java 的安全性。
事實證明我們可以。下面是 Elm 中的一個帶有類型注釋的函數(shù):
add : Int -> Int -> Int add x y = x + y
請注意類型信息是在多帶帶的代碼行上面的,而正是這樣的分割使得其有所不同。
現(xiàn)在你可能認為類型注釋有錯訓(xùn)。 第一次見到它的時候。 大都認為第一個 -> 應(yīng)該是一個逗號。可以加上隱含的括號,代碼就清晰多了:
add : Int -> (Int -> Int)
上例 add 是一個函數(shù),它接受類型為 Int 的單個參數(shù),并返回一個函數(shù),該函數(shù)接受單個參數(shù) Int類型 并返回一個 Int 類型的結(jié)果。
以下是一個帶括號類型注釋的代碼:
doSomething : String -> (Int -> (String -> String)) doSomething prefix value suffix = prefix ++ (toString value) ++ suffix
這里 doSomething 是一個函數(shù),它接受 String 類型的單個參數(shù),然后返回一個函數(shù),該函數(shù)接受 Int 類型的單個參數(shù),然后返回一個函數(shù),該函數(shù)接受 String 類型的單個參數(shù),并返回一個字符串。
注意為什么每個方法都只接受一個參數(shù)呢? 這是因為每個方法在 Elm 里面都是柯里化。
因為括號總是指向右邊,它們是不必要的,簡寫如下:
doSomething : String -> Int -> String -> String
當我們將函數(shù)作為參數(shù)傳遞時,括號是必要的。如果沒有它們,類型注釋將是不明確的。例如:
takes2Params : Int -> Int -> String takes2Params num1 num2 = -- do something
非常不同于:
takes1Param : (Int -> Int) -> String takes1Param f = -- do something
takes2Param 函數(shù)需要兩個參數(shù),一個 Int 和另一個 Int,而takes1Param 函數(shù)需要一個參數(shù),這個參數(shù)為函數(shù), 這個函數(shù)需要接受兩個 Int 類型參數(shù)。
下面是 map 的類型注釋:
map : (a -> b) -> List a -> List b map f list = // ...
這里需要括號,因為 f 的類型是(a -> b),也就是說,函數(shù)接受類型 a 的單個參數(shù)并返回類型 b 的某個函數(shù)。
這里類型 a 是任何類型。當類型為大寫形式時,它是顯式類型,例如 String。當一個類型是小寫時,它可以是任何類型。這里 a 可以是字符串,也可以是 Int。
如果你看到 (a -> a) 那就是說輸入類型和輸出類型必須是相同的。它們是什么并不重要,但必須匹配。
但在 map 這一示例中,有這樣一段 (a -> b)。這意味著它既能返回一個不同的類型,也能返回一個相同的類型。
但是一旦 a 的類型確定了,a 在整段代碼中就必須為這個類型。例如,如果 a 是一個 Int,b 是一個 String,那么這段代碼就相當于:
(Int -> String) -> List Int -> List String
這里所有的 a 都換成了 Int,所有的 b 都換成了 String。
List Int 類型意味著一個值都為 Int 類型的列表, List String 意味著一個值都為 String 類型的列表。如果你已經(jīng)在 Java 或者其他的語言中使用過泛型,那么這個概念你應(yīng)該是熟悉的
函數(shù)式 JavaScriptJavaScript 擁有很多類函數(shù)式的特性但它沒有純性,但是我們可以設(shè)法得到一些不變量和純函數(shù),甚至可以借助一些庫。
但這并不是理想的解決方法。如果你不得不使用純特性,為何不直接考慮函數(shù)式語言?
這并不理想,但如果你必須使用它,為什么不從函數(shù)式語言中獲得一些好處呢?
不可變性(Immutability)首先要考慮的是不變性。在ES2015或ES6中,有一個新的關(guān)鍵詞叫const,這意味著一旦一個變量被設(shè)置,它就不能被重置:
const a = 1; a = 2; // 這將在Chrome、Firefox或 Node中拋出一個類型錯誤,但在Safari中則不會
在這里,a 被定義為一個常量,因此一旦設(shè)置就不能更改。這就是為什么 a = 2 拋出異常。
const 的缺陷在于它不夠嚴格,我們來看個例子:
const a = { x: 1, y: 2 }; a.x = 2; // 沒有異常 a = {}; // 報錯
注意到 a.x = 2 沒有拋出異常。const 關(guān)鍵字唯一不變的是變量 a, a 所指向的對象是可變的。
那么Javascript中如何獲得不變性呢?
不幸的是,我們只能通過一個名為 Immutable.js 的庫來實現(xiàn)。這可能會給我們帶來更好的不變性,但遺憾的是,這種不變性使我們的代碼看起來更像 Java 而不是 Javascript。
柯里化與組合 (curring and composition)在本系列的前面,我們學(xué)習(xí)了如何編寫柯里化函數(shù),這里有一個更復(fù)雜的例子:
const f = a => b => c => d => a + b + c + d
我們得手寫上述柯里化的過程,如下:
console.log(f(1)(2)(3)(4)); // prints 10
括號如此之多,但這已經(jīng)足夠讓Lisp程序員哭了。有許多庫可以簡化這個過程,我最喜歡的是 Ramda。
使用 Ramda 簡化如下:
const f = R.curry((a, b, c, d) => a + b + c + d); console.log(f(1, 2, 3, 4)); // prints 10 console.log(f(1, 2)(3, 4)); // also prints 10 console.log(f(1)(2)(3, 4)); // also prints 10
函數(shù)的定義并沒有好多少,但是我們已經(jīng)消除了對那些括號的需要。注意,調(diào)用 f 時,可以指定任意參數(shù)。
重寫一下之前的 mult5AfterAdd10 函數(shù):
const add = R.curry((x, y) => x + y); const mult5 = value => value * 5; const mult5AfterAdd10 = R.compose(mult5, add(10));
事實上 Ramda 提供了很多輔助函數(shù)來做些簡單常見的運算,比如R.add以及R.multiply。以上代碼我們還可以簡化:
const mult5AfterAdd10 = R.compose(R.multiply(5), R.add(10));Map, Filter and Reduce
Ramda 也有自己的 map、filter和 reduce 版本。雖然這些函數(shù)存在于數(shù)組中。這幾個函數(shù)是在 Array.prototype 對象中的,而在 Ramda 中它們是柯里化的
const isOdd = R.flip(R.modulo)(2); const onlyOdd = R.filter(isOdd); const isEven = R.complement(isOdd); const onlyEven = R.filter(isEven); const numbers = [1, 2, 3, 4, 5, 6, 7, 8]; console.log(onlyEven(numbers)); // prints [2, 4, 6, 8] console.log(onlyOdd(numbers)); // prints [1, 3, 5, 7]
R.modulo 接受2個參數(shù),被除數(shù)和除數(shù)。
isOdd 函數(shù)表示一個數(shù)除 2 的余數(shù)。若余數(shù)為 0,則返回 false,即不是奇數(shù);若余數(shù)為 1,則返回 true,是奇數(shù)。用 R.filp 置換一下 R.modulo 函數(shù)兩個參數(shù)順序,使得 2 作為除數(shù)。
isEven 函數(shù)是 isOdd 函數(shù)的補集。
onlyOdd 函數(shù)是由 isOdd 函數(shù)進行斷言的過濾函數(shù)。當它傳入最后一個參數(shù),一個數(shù)組,它就會被執(zhí)行。
同理,onlyEven 函數(shù)是由 isEven 函數(shù)進行斷言的過濾函數(shù)。
當我們給函數(shù) onlyEven 和 onlyOd 傳入 numbers,isEven 和 isOdd 獲得了最后的參數(shù),然后執(zhí)行最終返回我們期望的數(shù)字。
Javascript的缺點所有的庫和語言增強都已經(jīng)得到了Javascript 的發(fā)展,但它仍然面臨著這樣一個事實:它是一種強制性的語言,它試圖為所有人提供所有的東西。
大多數(shù)前端開發(fā)人員都不得不使用 Javascript,因為這旨瀏覽器也識別的語言。相反,它們使用不同的語言編寫,然后編譯,或者更準確地說,是把其它語言轉(zhuǎn)換成 Javascript。
CoffeeScript 是這類語言中最早的一批。目前,TypeScript 已經(jīng)被 Angular2 采用,Babel可以將這類語言編譯成 JavaScript,越來越多的開發(fā)者在項目中采用這種方式。
但是這些語言都是從 Javascript 開始的,并且只稍微改進了一點。為什么不直接從純函數(shù)語言轉(zhuǎn)換到Javascript呢?
未來期盼我們不可能知道未來會怎樣,但我們可以做一些有根據(jù)的猜測。以下是作者的一些看法:
能轉(zhuǎn)換成 JavaScript 這類語言會有更加豐富及健壯。
已有40多年歷史的函數(shù)式編程思想將被重新發(fā)現(xiàn),以解決我們當前的軟件復(fù)雜性問題。
目前的硬件,比如廉價的內(nèi)存,快速的處理器,使得函數(shù)式技術(shù)普及成為可能。
PU不會變快,但是內(nèi)核的數(shù)量會持續(xù)增加。
可變狀態(tài)將被認為是復(fù)雜系統(tǒng)中最大的問題之一。
希望這系列文章能幫助你更好容易更好幫助你理解函數(shù)式編程及優(yōu)勢,作者相信函數(shù)式編程是未來趨勢,大家有時間可以多多了解,接著提升你們的技能,然后未來有更好的出路。
原文:
https://medium.com/@cscalfani...
https://medium.com/@cscalfani...
編輯中可能存在的bug沒法實時知道,事后為了解決這些bug,花了大量的時間進行l(wèi)og 調(diào)試,這邊順便給大家推薦一個好用的BUG監(jiān)控工具Fundebug。
你的點贊是我持續(xù)分享好東西的動力,歡迎點贊!
歡迎加入前端大家庭,里面會經(jīng)常分享一些技術(shù)資源。文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/100504.html
摘要:想閱讀更多優(yōu)質(zhì)文章請猛戳博客一年百來篇優(yōu)質(zhì)文章等著你本系列的第一篇學(xué)會使用函數(shù)式編程的程序員第部分組合函數(shù)作為程序員,我們是懶惰的??吕锘址Q部分求值。一旦使用函數(shù)式語言,任何東西都是不可變的。在函數(shù)式語言中,這個函數(shù)稱為。 showImg(https://segmentfault.com/img/bVblEzw?w=800&h=355); 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一...
摘要:函數(shù)式編程的目標是盡量寫更多的純函數(shù),并將其與程序的其他部分隔離開來。在函數(shù)式編程中,是非法的。函數(shù)式編程使用參數(shù)保存狀態(tài),最好的例子就是遞歸。函數(shù)式編程使用遞歸進行循環(huán)。在函數(shù)式編程中,函數(shù)是一級公民。 showImg(https://segmentfault.com/img/bVblxCO?w=1600&h=710); 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等...
摘要:今天這篇文章主要介紹函數(shù)式編程的思想。函數(shù)式編程通過最小化變化使得代碼更易理解。在函數(shù)式編程里面,組合是一個非常非常非常重要的思想??梢钥吹胶瘮?shù)式編程在開發(fā)中具有聲明模式。而函數(shù)式編程旨在盡可能的提高代碼的無狀態(tài)性和不變性。 最開始接觸函數(shù)式編程的時候是在小米工作的時候,那個時候看老大以前寫的代碼各種 compose,然后一些 ramda 的一些工具函數(shù),看著很吃力,然后極力吐槽函數(shù)式...
摘要:本文與大家分享一些編程語言的入門書籍,其中不乏經(jīng)典。全書貫穿的主體是如何思考設(shè)計開發(fā)的方法,而具體的編程語言,只是提供一個具體場景方便介紹的媒介。入門入門容易理解而且讀起來幽默風(fēng)趣,對于編程初學(xué)者和語言新手而言是理想的書籍。 本文與大家分享一些Python編程語言的入門書籍,其中不乏經(jīng)典。我在這里分享的,大部分是這些書的英文版,如果有中文版的我也加上了。有關(guān)書籍的介紹,大部分截取自是官...
摘要:所以我覺得函數(shù)式編程領(lǐng)域更像學(xué)者的領(lǐng)域。函數(shù)式編程的原則是完善的,經(jīng)過了深入的研究和審查,并且可以被驗證。函數(shù)式編程是編寫可讀代碼的最有效工具之一可能還有其他。我知道很多函數(shù)式編程編程者會認為形式主義本身有助于學(xué)習(xí)。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson?。 禮ou-Dont-Know-JS》作者 關(guān)于譯者:這是一個流淌著滬江血液...
閱讀 3175·2021-10-11 10:58
閱讀 2045·2021-09-24 09:47
閱讀 540·2019-08-30 14:19
閱讀 1772·2019-08-30 13:58
閱讀 1469·2019-08-29 15:26
閱讀 662·2019-08-26 13:45
閱讀 2174·2019-08-26 11:53
閱讀 1801·2019-08-26 11:30