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