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