摘要:本文為本人參與的前端早讀課公眾號(hào)成為函數(shù)式碼農(nóng)系列翻譯的第五篇,第六篇仍在翻譯中,以下為其它五篇的地址。然而,關(guān)于多線程,存在兩個(gè)主要的問題。首先,它們必須是純函數(shù)。類型意味著一個(gè)值都為類型的列表,意味著一個(gè)值都為類型的列表。
本文為本人參與的前端早讀課公眾號(hào)《成為函數(shù)式碼農(nóng)》系列翻譯的第五篇,第六篇仍在翻譯中,以下為其它五篇的地址。
成為一名函數(shù)式碼農(nóng)系列之一
成為一名函數(shù)式碼農(nóng)系列之二
成為一名函數(shù)式碼農(nóng)系列之三
成為一名函數(shù)式碼農(nóng)系列之四
成為一名函數(shù)式碼農(nóng)系列之六
原文地址 譯者:墨白 校對:野草
剛開始學(xué)習(xí)函數(shù)式編程時(shí),理解函數(shù)式編程的核心概念是最重要的,有些時(shí)候也是最難的一步。但其實(shí)沒必要一定如此。這個(gè)沒有正確的答案。
引用透明(referential transparency)引用透明是一個(gè)富有想象力的優(yōu)秀術(shù)語,它是用來描述純函數(shù)可以被它的表達(dá)式安全的替換。通過一個(gè)例子來幫助理解這個(gè)術(shù)語。
在代數(shù)中,你有一個(gè)下面的公式:
y = x + 10
然后被告知:
x = 3
你可以把x的值帶入到之前的方程中,得到:
y = 3 + 10
注意這個(gè)方程仍然是有效的。我們可以利用純函數(shù)做一些相同類型的替換。
下面是一個(gè)Elm的方法,在傳入的字符串兩邊加上單引號(hào):
quote str = """ ++ str ++ """
下面是使用它的代碼:
findError key = "Unable to find " ++ (quote key)
代碼中,當(dāng)查詢key值失敗時(shí),findError構(gòu)建了一個(gè)報(bào)錯(cuò)信息。
因?yàn)?b>quote方法是純函數(shù),我們可以簡單地將quote函數(shù)體(僅僅只是個(gè)表達(dá)式)替換掉在findError中的方法調(diào)用:
findError key = "Unable to find " ++ (""" ++ str ++ """)
這就是我所說的“反向重構(gòu)”(它對我而言有更多的意義),一個(gè)可以被程序員或者程序(例如編譯器和測試程序)用來推理代碼的過程。
這在推導(dǎo)遞歸函數(shù)時(shí)尤其有用。
執(zhí)行順序大部分程序是單線程的,即有且只有一段代碼在當(dāng)前執(zhí)行。即使你有多線程的程序,大部分程序仍然阻塞等待I/O去完成,例如,file,network等等。
這也是當(dāng)我們編寫代碼的時(shí)候,我們很自然考慮按次序來編寫代碼:
1. 拿到面包 2. 把2片面包放入烤面包機(jī) 3. 選擇加熱時(shí)間 4. 按下開始按鈕 5. 等待面包片彈出 6. 取出烤面包 7. 拿黃油 8. 拿黃油刀 9. 制作黃油面包
在這個(gè)例子中,有兩個(gè)獨(dú)立的操作:拿黃油以及加熱面包。它們在step9時(shí)開始變得相互依賴。
我們可以在step1到6的時(shí)候做step7和8因?yàn)樗鼈冎g相互獨(dú)立。
但當(dāng)我們開始做的時(shí)候,事情開始復(fù)雜了:
線程1: -------- 1. 拿到面包 2. 把2片面包放入烤面包機(jī) 3. 選擇加熱時(shí)間 4. 向下推桿 5. 等待面包片彈出 6. 取出烤面包 線程2: 1. 拿黃油 2. 拿黃油刀 3. 等待線程1完成 4. 制作黃油面包
如果線程1失敗,線程2怎么辦?怎么協(xié)調(diào)這兩個(gè)線程?烤面包這一步驟在哪個(gè)線程運(yùn)行:線程1,線程2或者兩者?
我們完全可以不去思考這些復(fù)雜的,只讓我們的程序單線程運(yùn)行,這更簡單。
但是,只要能夠提升我們程序的效率,那就是值得的,我們要付出努力來寫好多線程程序。
然而,關(guān)于多線程,存在兩個(gè)主要的問題。首先,多線程程序非常難寫、讀、理解、測試以及debug。
第二,一些語言,例如JavaScript,并不支持多線程,就算有些語言支持多線程,對它的支持也很弱。
但是,如果運(yùn)行順序并不重要并且一切都是并行執(zhí)行的呢?
盡管這聽起來有些瘋狂,但其實(shí)并不像聽起來那么混亂。讓我們來看一下Elm的代碼來形象的理解它:
buildMessage message value = let upperMessage = String.toUpper message quotedValue = """ ++ value """ in upperMessage ++ ": " ++ value
這里的buildMessage接受message和value,然后,生成大寫的message,冒號(hào)和在單引號(hào)中value。
注意到upperMessage和quotedValue是獨(dú)立的。我們怎么知道的呢?
對于獨(dú)立,有兩點(diǎn)必須必須滿足。首先,它們必須是純函數(shù)。這很重要,因?yàn)樗鼈儽仨毑粫?huì)被其它方法的運(yùn)行影響到。
如果它們不是純函數(shù),那么我們永遠(yuǎn)不可能知道它們是否獨(dú)立。那種情況下,我們不得不依賴于它們在程序中調(diào)用的順序來確定它們的執(zhí)行順序。這是所有命令式語言的工作原理。
第二點(diǎn)必須滿足的就是一個(gè)函數(shù)的輸出值不能作為其它函數(shù)的輸入值。如果存在這種情況,那么我們不得不等待其中一個(gè)完成才能執(zhí)行下一個(gè)。
在上面的代碼示例中,upperMessage和quotedValue兩者都是純的并且沒有一個(gè)需要依賴其它的輸出。
因此,這兩個(gè)方法可以在任何順序下執(zhí)行。
編譯器可以自行決定執(zhí)行的順序,而不需要程序員的人為參與。這只有在純函數(shù)式編程語言中才適用,因?yàn)樵谝话憔幊陶Z言中是很難去(不是不可能)預(yù)估不同順序帶來的副作用。
純函數(shù)式語言里面,執(zhí)行的順序是可以由編譯器決定的
鑒于無法一再加快CPU的運(yùn)行速度,這一做法是非常有利的。生產(chǎn)商也不斷增加CPU內(nèi)核芯片的數(shù)量,這就意味著可以在硬件這一層面實(shí)現(xiàn)代碼的并行處理。
但遺憾的是,我們無法通過命令式的語言充分利用這些芯片,而只是發(fā)揮了它們很小一部分的功能。如果要充分利用就要徹底改變程序的體系結(jié)構(gòu)。
使用純函數(shù)語言,我們就有希望在不改變?nèi)魏未a的情況下充分地發(fā)揮CPU芯片的功能并取得良好成效。
類型注釋在靜態(tài)類型語言中,類型是內(nèi)聯(lián)定義的。 這里通過一些Java代碼來說明:
public static String quote(String str) {return """ + str + """;}
private final MapgetPerson(Map people, Integer personId) {
// ...
}
我已經(jīng)給定義類型的字段加粗了以使其更加顯眼,但它們看來去仍然和函數(shù)定義糾纏在一起。你不得不很小心地去找到這些變量的名字。
如果是用神奇的動(dòng)態(tài)類型語言,這不是一個(gè)問題。在JavaScript中,我們這樣寫代碼:
var getPerson = function(people, personId) { // ... };
這樣的代碼沒有任何的繁瑣的類型信息更易閱讀。唯一的問題就是我們放棄了類型檢測的安全特性。我們能夠很簡單的傳入這些參數(shù),例如,一個(gè)Number類型的people以及一個(gè)Object類型的personId。
除非程序運(yùn)行,否則我們發(fā)現(xiàn)不了這樣的問題,而這樣的問題也可能在代碼上線之后幾個(gè)月才能出現(xiàn)。而這樣的問題在Java中不會(huì)出現(xiàn),因?yàn)樗鼰o法通過編譯。
但是,假如我們能同時(shí)擁有這兩者的優(yōu)異點(diǎn)呢?JavaScript的語法簡單性以及Java的安全性。
其實(shí)我們是可以的。下面是一個(gè)帶類型注釋的用Elm寫的方法:
add : Int -> Int -> Int add x y = x + y
請注意類型信息是在多帶帶的代碼行上面的。而正是這樣的分割使得其有所不同。
現(xiàn)在你可能認(rèn)為類型注釋有錯(cuò)字。 我知道我第一次見到它的時(shí)候。 我認(rèn)為第一個(gè) - >應(yīng)該是一個(gè)逗號(hào)。 但并沒有錯(cuò)別字。
當(dāng)你看到它加上隱含的括號(hào),代碼就清晰多了:
add : Int -> (Int -> Int)
這表示,add是一個(gè)方法,它接受單個(gè)Int類型的參數(shù),返回一個(gè)方法,這個(gè)方法接受一個(gè)Int類型的參數(shù),并且返回一個(gè)Int類型的值。
這里還有一個(gè)帶括號(hào)類型注釋的代碼:
doSomething : String -> (Int -> (String -> String)) doSomething prefix value suffix = prefix ++ (toString value) ++ suffix
上面的代碼表示doSomething是一個(gè)方法,它接受單個(gè)類型為String的參數(shù)并且返回一個(gè)函數(shù),返回的函數(shù)接受單個(gè)類型為Int的參數(shù),并且再次返回一個(gè)函數(shù),這次返回的函數(shù)接受一個(gè)類型為String的參數(shù),并且返回一個(gè)String。
注意為什么每個(gè)方法都只接受一個(gè)參數(shù)呢?這是因?yàn)槊總€(gè)方法在Elm里面都是柯里化。
由于括號(hào)總是隱含在右邊,它們并不是必須。所以我們可以簡寫成:
doSomething : String -> Int -> String -> String
當(dāng)我們傳遞函數(shù)作為參數(shù)時(shí),括號(hào)是必要的。 沒有它們,類型注釋將是不明確的。 例如:
takes2Params : Int -> Int -> String takes2Params num1 num2 = -- do something
與下面的并不同:
takes1Param : (Int -> Int) -> String takes1Param f = -- do something
takes2Param是一個(gè)接受兩個(gè)參數(shù)的函數(shù),兩個(gè)參數(shù)都是Int類型。然而,takes1Param需要接受一個(gè)參數(shù),這個(gè)參數(shù)為函數(shù),而函數(shù)需要接受兩個(gè)Int類型的參數(shù)。
下面是map的類型注釋:
map : (a -> b) -> List a -> List b map f list = // ...
上面需要括號(hào)是因?yàn)?b>f是(a -> b)類型,即接受類型為a的單個(gè)參數(shù)并返回類型為b的某個(gè)函數(shù).
這里類型a是代指任何類型。 當(dāng)類型是大寫時(shí),它是一個(gè)顯式類型,例如,String。 當(dāng)類型為小寫時(shí),它可以是任何類型。 這里的a可以是String,但也可以是Int。
如果你看到(a -> a),那么,就是指input類型以及output類型是相同的。它們到底是什么類型并不重要,重要的是它們必須匹配。
但在map這一示例中,有這樣一段(a -> b)。這意味著它既能返回一個(gè)不同的類型,也能返回一個(gè)相同的類型。
但是一旦a的類型確定了,(TODO the whole signature)a在整段代碼中就必須為這個(gè)類型。例如,如果a是一個(gè)Int,b是一個(gè)String,那么這段代碼就相當(dāng)于:
(Int -> String) -> List Int -> List String
上面就是所有的a都被替換成Int,所有的b都被替換成String。
List Int類型意味著一個(gè)值都為Int類型的列表,List String意味著一個(gè)值都為String類型的列表。如果你已經(jīng)在Java或者其他的語言中使用過泛型,那么這個(gè)概念你應(yīng)該是熟悉的。
在這個(gè)系列文章的最后,我將會(huì)探討如何使用你在日常生活中學(xué)到的東西,例如,函數(shù)式編程以及Elem。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/82215.html
摘要:基礎(chǔ)深度學(xué)習(xí)概念備忘錄后端掘金基礎(chǔ)深度學(xué)習(xí)概念備忘錄翻譯自。否則,試想在你捧著某出版社剛剛翻譯出來的高效編程苦規(guī)范及相關(guān)文檔前端掘金官方規(guī)范歲程序員的獨(dú)家面試經(jīng)歷閱讀掘金創(chuàng)業(yè)失敗后,在找工作。 基礎(chǔ)深度學(xué)習(xí)概念備忘錄 - 后端 - 掘金基礎(chǔ)深度學(xué)習(xí)概念備忘錄翻譯自DeepLearning Cheat Sheet。筆者還是菜鳥一枚,若有謬誤請多多賜教,另外如果希望了解更多機(jī)器學(xué)習(xí)&深度學(xué)...
摘要:鋪墊已了,進(jìn)入今天的正題,貓薦書系列之五高性能編程本書適合已入門還想要進(jìn)階和提高的讀者閱讀。書中列舉了兩個(gè)慘痛的教訓(xùn)華爾街公司騎士資本由于軟件升級引入的錯(cuò)誤,損失億美元公司小時(shí)全球中斷的嚴(yán)重事故。 showImg(https://segmentfault.com/img/bVbm92w?w=6720&h=4480); 稍微關(guān)心編程語言的使用趨勢的人都知道,最近幾年,國內(nèi)最火的兩種語言非...
摘要:鋪墊已了,進(jìn)入今天的正題,貓薦書系列之五高性能編程本書適合已入門還想要進(jìn)階和提高的讀者閱讀。書中列舉了兩個(gè)慘痛的教訓(xùn)華爾街公司騎士資本由于軟件升級引入的錯(cuò)誤,損失億美元公司小時(shí)全球中斷的嚴(yán)重事故。 showImg(https://segmentfault.com/img/bVbm92w?w=6720&h=4480); 稍微關(guān)心編程語言的使用趨勢的人都知道,最近幾年,國內(nèi)最火的兩種語言非...
閱讀 1692·2023-04-25 20:16
閱讀 3874·2021-10-09 09:54
閱讀 2708·2021-09-04 16:40
閱讀 2525·2019-08-30 15:55
閱讀 842·2019-08-29 12:37
閱讀 2746·2019-08-26 13:55
閱讀 2914·2019-08-26 11:42
閱讀 3158·2019-08-23 18:26