自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

為什么我們要學(xué)習(xí)Haskell這樣的編程語(yǔ)言

開發(fā) 開發(fā)工具
最近的幾個(gè)月,我一直在學(xué)習(xí)一種叫Haskell的編程語(yǔ)言。由于里面有太多的從未遇到的編程概念,整個(gè)過(guò)程就像是完全重新學(xué)習(xí)如何編程。在i.TV網(wǎng)站上,我寫了很多JavaScript(node.js和前端代碼)。雖然有不少的函數(shù)式/haskell式的編程模式不能引用進(jìn)來(lái),但仍有大量的技術(shù)思想讓我在使用javascript編程語(yǔ)言時(shí)受益不少。

最近的幾個(gè)月,我一直在學(xué)習(xí)一種叫Haskell的編程語(yǔ)言。由于里面有太多的從未遇到的編程概念,整個(gè)過(guò)程就像是完全重新學(xué)習(xí)如何編程。在i.TV網(wǎng)站上,我寫了很多JavaScript(node.js和前端代碼)。雖然有不少的函數(shù)式/haskell式的編程模式不能引用進(jìn)來(lái),但仍有大量的技術(shù)思想讓我在使用javascript編程語(yǔ)言時(shí)受益不少。

你會(huì)發(fā)現(xiàn)Haskell庫(kù)里有能夠處理各種事情的各種各樣的函數(shù)。起初我以為這些只是一種技術(shù)上的積累,但隨后我認(rèn)識(shí)到,這些函數(shù)相比起其它語(yǔ)言里的函數(shù),它們能應(yīng)用到形式更廣泛的問題中。這使得它們更有價(jià)值,因?yàn)槲覀兌疾惶矚g對(duì)一些常見的問題還不得不自己去寫解決方案。

這些函數(shù)是可以相互組合1的:它們能針對(duì)性的解決某些問題,而不對(duì)你的代碼做任何依賴,所以,你可以拼裝它們,組合成一個(gè)能夠解決你的大問題的東西。

高階函數(shù)(Higher Order Functions)

在Haskell語(yǔ)言中,最多的被反復(fù)使用的函數(shù)都是高階函數(shù)(higher order functions)——能以函數(shù)作為參數(shù)、能返回函數(shù)的函數(shù)。這使得它們具有固有的靈活性。下面是一個(gè)不太靈活的函數(shù):它計(jì)算一個(gè)數(shù)組里等于某個(gè)值的元素的個(gè)數(shù)。

  1. // 不靈活  
  2. function countMatching(array, value) {  
  3.     var counted = 0 
  4.     for (var i = 0; i < array.length; i++) {  
  5.         if (array[i] == value)  
  6.             counted++  
  7.     }  
  8.     return counted  
  9. }  
  10.  
  11. // == 2  
  12. countMatching([1,3,3,4,5], 3)  

它不靈活,因?yàn)樗荒苡脕?lái)計(jì)算一個(gè)數(shù)組中精確匹配某個(gè)值的元素的個(gè)數(shù)。

下面是一個(gè)靈活一些的版本,它能接受一個(gè)函數(shù),而不是一個(gè)值,作為參數(shù)。我們可以用它來(lái)對(duì)任何數(shù)據(jù)、任何對(duì)象進(jìn)行比較。

  1. // more flexible  
  2. function count(array, matching) {  
  3.     var counted = 0 
  4.     for (var i = 0; i < array.length; i++) {  
  5.         if (matching(array[i]))  
  6.             counted++  
  7.     }  
  8.     return counted  
  9. }  
  10.  
  11. // == 2, same as first example  
  12. count([1,3,3,4,5], function(num) {  
  13.     return (num == 3)  
  14. })  
  15.  
  16. // == 2, now we can use our functions for ANY kind of items or match test!  
  17. count([{name:"bob"}, {name:"henry"}, {name:"jon"}], function(obj) {  
  18.     return (obj.name.length < 4)  
  19. }) 

因?yàn)楦唠A函數(shù)更具靈活性,你就更少有機(jī)會(huì)去寫它們,因?yàn)槟阋坏┠銓懗梢粋€(gè),你可以它應(yīng)用到各種不同的情況中。

可重復(fù)利用的比較函數(shù)

你可能注意到了,count函數(shù)的寫法比countMatching更冗長(zhǎng)。但是,雖然count函數(shù)可復(fù)用了,但比較函數(shù)2卻不可復(fù)用。如果是一些簡(jiǎn)單的情況,這就足夠了,但經(jīng)常,我們會(huì)需要更復(fù)雜的比較方法的函數(shù)。這樣的函數(shù)不僅僅可用于計(jì)數(shù),它們可以用于任何事情上,一但你寫成或找到了這樣的函數(shù),從長(zhǎng)期的角度看,它們會(huì)節(jié)省你大量的時(shí)間和調(diào)試功夫。

讓我們來(lái)定義一個(gè)可復(fù)用的比較函數(shù),達(dá)到我們的目的。==不是一個(gè)函數(shù)。我們是否可以定義一個(gè)eq函數(shù)來(lái)幫我們完成類似的事情呢?

  1. function eq(a, b) {  
  2.     return (a == b)  
  3. }  
  4.  
  5. count([1,3,3,4,5], function(num) {  
  6.     return eq(3, num)  
  7. })  

我們向前邁進(jìn)了一步:我們用了一個(gè)庫(kù)函數(shù)來(lái)完成比較任務(wù),而不是使用我們現(xiàn)寫的代碼。如果eq函數(shù)很復(fù)雜,我們可以測(cè)試它并可以在其它的地方復(fù)用它。

但這使代碼變得冗長(zhǎng),因?yàn)閏ount函數(shù)的參數(shù)是一個(gè)只需要一個(gè)參數(shù)——數(shù)組元素——的函數(shù),而eq函數(shù)卻需要兩個(gè)參數(shù)。我還是要定義我們自己的匿名函數(shù)。讓我們來(lái)簡(jiǎn)化一下這些代碼。

  1. function makeEq(a) {  
  2.     // countMatchingWith wants a function that takes   
  3.     // only 1 argument, just like the one we're returning  
  4.     return function(b) {  
  5.         return eq(a, b)  
  6.     }  
  7. }  
  8.  
  9. // now it's only on one line!  
  10. count([1,3,3,4,5], makeEq(3))  

我們寫了一個(gè)兼容count函數(shù)的函數(shù)(一個(gè)參數(shù)——數(shù)組元素——返回true或false)??雌饋?lái)就像是count函數(shù)調(diào)用的是eq(3, item)。這叫做偏函數(shù)用法(partial function application)。
偏函數(shù)用法(Partial Application)

偏函數(shù)用法(Partial Function Application)是指創(chuàng)建一個(gè)調(diào)用另外一個(gè)部分參數(shù)已經(jīng)預(yù)置的函數(shù)的函數(shù)的用法。這樣,它就能被別的地方,比如count函數(shù),以更少的參數(shù)形式來(lái)調(diào)用。我們?cè)趍akeEq函數(shù)里已經(jīng)實(shí)現(xiàn)了這些,但是,我們并不想針對(duì)我們各種功能開發(fā)出各種版本的makeX(比如makeEqt,makeGt,makeLt等)函數(shù)。讓我們來(lái)找一種方法能通用于各種形式的函數(shù)。

  1. function applyFirst(f, a) {  
  2.     return function(b) {  
  3.         return f.call(null, a, b)  
  4.     }  
  5. }  
  6.  
  7. count([1,3,3,4,5], applyFirst(eq, 3))  

現(xiàn)在我們不再需要一個(gè)makeEq函數(shù)了。任何2個(gè)參數(shù)的庫(kù)函數(shù),我們都可以按這種方式調(diào)用。通過(guò)偏函數(shù)用法,使得定義即使是諸如==這樣簡(jiǎn)單功能的各種函數(shù)都變得十分有意義,我們可以在高階函數(shù)中更容易的使用它們。

對(duì)那些超過(guò)2個(gè)參數(shù)的函數(shù)如何辦呢?下面的這一版本3能讓我們接受任意多的參數(shù),高階函數(shù)可以自己追加參數(shù)。

  1. function apply(f) {  
  2.     var args = Array.prototype.slice.call(arguments, 1)  
  3.     return function(x) {  
  4.         return f.apply(null, args.concat(x))  
  5.     }  
  6. }  
  7.  
  8. function propertyEquals(propertyName, value, obj) {  
  9.     return (obj[propertyName] == value)  
  10. }  
  11.  
  12. count([{name:"bob"},{name:"john"}], apply(propertyEquals, "name""bob")) // == 1  

我們預(yù)置了2個(gè)參數(shù),“name” 和 “bob”,count函數(shù)補(bǔ)足了***一個(gè)參數(shù)來(lái)完成整個(gè)調(diào)用。偏函數(shù)用法使我們能接受各樣的函數(shù)為參數(shù),例如eq,然后把它們用于各樣的高階函數(shù),例如count,以此來(lái)解決我們特定的問題。

配合ES5的 Map 和 Filter 功能函數(shù)的偏函數(shù)用法

ES5里有很多非常好的高階函數(shù),underscore里的數(shù)量更多。讓我們看看filter函數(shù)——一個(gè)接收比較函數(shù)、過(guò)濾數(shù)組內(nèi)容的函數(shù)。

  1. // this equals [1,3,3]  
  2. [1,3,3,4,5].filter(function(num) {  
  3.     return (num < 4)  
  4. })  

讓我們把它替換成一個(gè)可以復(fù)用的比較函數(shù)lt (less than)。

  1. function lt(a, b) {  
  2.     return (a < b)  
  3. }  
  4.  
  5. [1,3,3,4,5].filter(apply(lt, 4))  

看上去添加這個(gè)lt函數(shù)的做法有點(diǎn)傻,但是,我們可以使用偏函數(shù)用法來(lái)創(chuàng)造一個(gè)很簡(jiǎn)練的比較函數(shù),當(dāng)這個(gè)比較函數(shù)變的很復(fù)雜的時(shí)候,我們就能從對(duì)它的復(fù)用過(guò)程中獲得好處。

map函數(shù)能讓你把數(shù)組里的一個(gè)東西變成另外一個(gè)東西。

  1. var usersById = {"u1":{name:"bob"}, "u2":{name:"john"}}  
  2. var user = {name:"sean", friendIds: ["u1""u2"]}  
  3.  
  4. // == ["bob", "john"]  
  5. function friendsNames(usersById, user) {  
  6.     return user.friendIds.map(function(id) {  
  7.         return usersById[id].name  
  8.     })  
  9. }  

我們寫一個(gè)可以復(fù)用的map變換函數(shù),就像之前我們的可復(fù)用比較函數(shù)一樣。讓我們寫一個(gè)叫做lookup的函數(shù)。

  1. function lookup(obj, key) {  
  2.     return obj[key]  
  3. }  
  4.  
  5. // == [{name:"bob"}, {name:"john"}]  
  6. function friends(usersById, user) {  
  7.     return user.friendIds.map(apply(lookup, usersById))  
  8. }  

很接近要求,但我們需要的是名稱,而不是friend對(duì)象本身。如果我們?cè)賹懸粋€(gè)參數(shù)顛倒過(guò)來(lái)的 lookup函數(shù),通過(guò)第二次的map可以把它們的名稱取出來(lái)。

  1. function lookupFlipped(key, obj) {  
  2.     return lookup(obj, key)  
  3. }  
  4.  
  5. // == ["bob", "john"]  
  6. function friendsNames(usersById, user) {  
  7.     return friends(usersById, user)  
  8.             .map(apply(lookupFlipped, "name"))  
  9. }  

但是我不想定義這個(gè)lookupFlipped函數(shù),這樣干有點(diǎn)傻。這樣,我們來(lái)定義一個(gè)函數(shù),它接收參數(shù)的順序是從右到左,而不是從左到右,于是我們就能夠復(fù)用lookup了。

  1. function applyr(f) {  
  2.     var args = Array.prototype.slice.call(arguments, 1)  
  3.     return function(x) {  
  4.         return f.apply(null, [x].concat(args))  
  5.     }  
  6. }  
  7.  
  8. // == ["bob", "john"]  
  9. function friendsNames(usersById, user) {  
  10.     return friends(usersById, user)  
  11.             .map(applyr(lookup, "name")) // we can use normal lookup!  
  12. }  

applyr(lookup, "name")函數(shù)返回的函數(shù)只接受一個(gè)參數(shù)——那個(gè)對(duì)象——返回對(duì)象的名稱。我們不再需要反轉(zhuǎn)任何東西:我們可以按任何順序接受參數(shù)。

偏函數(shù)用法需要對(duì)一些常見的功能定義各種不同的函數(shù),就像lt函數(shù),但這正是我們的目的。你可以以偏函數(shù)用法把lt函數(shù)既用于count函數(shù),也可用于Array.filter函數(shù)。它們可以復(fù)用,可以組合使用。

函數(shù)組合

在之前的例子中,我們遍歷了數(shù)組兩次,一次用來(lái)獲取users,一次為了獲取names。如果能在一次map映射操作中同時(shí)做這兩件事情,效率會(huì)高很多。

  1. function friendsNames(usersById, user) {  
  2.     return user.friendIds.map(function(id) {  
  3.         var friend = lookup(usersById, id)  
  4.         return lookup(friend, "name")  
  5.     })  
  6. }  

我們得到***lookup的結(jié)果,把它第二次傳入lookup。函數(shù)組合意思是串聯(lián)多個(gè)函數(shù),組成一個(gè)新的函數(shù),每一次串聯(lián)都是把前一個(gè)函數(shù)的輸出當(dāng)作下一個(gè)函數(shù)的輸入。

讓我們來(lái)寫一個(gè)能這樣運(yùn)轉(zhuǎn)的高階函數(shù),利用它把friendsNames函數(shù)重寫成一個(gè)只需要單次map操作的函數(shù)。需要注意的是,函數(shù)串聯(lián)的執(zhí)行順序是從右到左的,就跟你寫出f(g(x))這樣的代碼的運(yùn)行方式一樣。

  1. function compose(f, g) {  
  2.     return function(x) {  
  3.         return f(g(x))  
  4.     }  
  5. }  
  6.  
  7. function friendsNames(usersById, user) {  
  8.     return user.friendIds.map(compose(applyr(lookup, "name"), apply(lookup, usersById)))  
  9. }  

對(duì)數(shù)組的遍歷只進(jìn)行了一次,只使用一次map操作,跟我們頭一個(gè)例子一樣。

我們不能使用我們寫出的friends函數(shù),因?yàn)樗劝巳绾稳〕鲆粋€(gè)friend的業(yè)務(wù)邏輯,也包含了map操作。friends函數(shù)是不能復(fù)用的,它的職責(zé)太多了——它是針對(duì)特定事物的。如果你們?cè)賹懸粋€(gè)friend函數(shù),讓它只map一個(gè)friend,寫一個(gè)name函數(shù),讓它返回對(duì)象的名稱呢?

  1. var friend = lookup // lookup 恰巧能干我們想要的事情。  
  2. var name = applyr(lookup, "name")  
  3.  
  4. function friendsNames(usersById, user) {  
  5.     // this line is now more semantic.   
  6.     return user.friends.map(compose(name, apply(friend, usersById)))  

相較于定義一個(gè)既包含轉(zhuǎn)換操作,又包含遍歷操作的friends函數(shù),我們只定義了一個(gè)可做轉(zhuǎn)換操作的friend函數(shù),而我們已經(jīng)有了map函數(shù)為我做變換操作。friend函數(shù)比f(wàn)riends函數(shù)更具復(fù)用性,因?yàn)樗俚奶囟I(yè)務(wù)邏輯,能在更多的情形中使用。

在這里你能找到更多的關(guān)于JavaScript里函數(shù)組合的信息。

函數(shù)式和功能單一化讓你的代碼庫(kù)更整潔

我發(fā)現(xiàn)我的很多的JavaScript代碼都是從無(wú)到有自己寫出來(lái)的。這不僅僅是說(shuō)比起使用現(xiàn)成的程序包要效率低,它還會(huì)暗藏更多的bug,更難閱讀和維護(hù)。使用高階函數(shù)和偏函數(shù)用法,我們可以寫出可復(fù)用的程序庫(kù),每個(gè)函數(shù)都精準(zhǔn)的對(duì)應(yīng)解決它們能解決的一部分問題。

隨著時(shí)間的推移,項(xiàng)目會(huì)變得越來(lái)越復(fù)雜,各部分越來(lái)越耦合,如果我們擁有的是一個(gè)能夠各自獨(dú)立測(cè)試不依賴的程序庫(kù),我們的項(xiàng)目會(huì)從中受益,變得更健康,更穩(wěn)定。

  1. 一種寬泛的組合。并不特指函數(shù)或?qū)ο蠼M合,只是一種你用小東西組建大東西的思想。
  2. “Matching functions”被稱作predicates,但我這里不想引入新的編程術(shù)語(yǔ)。
  3. 這里有更通用的apply實(shí)現(xiàn)。

英文:Learn You a Haskell: For Great Good?

原文鏈接:http://www.aqee.net/learn-you-a-haskell-for-great-good/

【編輯推薦】

  1. 最喜歡與最討厭的編程語(yǔ)言
  2. 你最喜愛的編程語(yǔ)言不夠好
  3. 為什么動(dòng)態(tài)類型語(yǔ)言相對(duì)比較慢?
  4. Java編程風(fēng)格與命名規(guī)范整理
  5. Java編程中“為了性能”盡量要做的幾點(diǎn)
責(zé)任編輯:林師授 來(lái)源: 外刊IT評(píng)論
相關(guān)推薦

2013-03-12 10:00:29

HaskellHaskell語(yǔ)言編程語(yǔ)言

2019-11-15 13:10:17

潘石屹Python語(yǔ)言

2010-11-03 09:22:00

C語(yǔ)言

2018-05-30 14:49:51

編程語(yǔ)言API語(yǔ)法

2017-04-05 18:10:05

R語(yǔ)言開發(fā)Ross

2022-12-12 07:30:59

編程語(yǔ)言架構(gòu)

2010-01-22 15:14:37

學(xué)習(xí)C++

2017-03-07 15:43:28

編程語(yǔ)言函數(shù)數(shù)據(jù)結(jié)構(gòu)

2021-02-23 10:19:46

編程技能開發(fā)

2023-02-10 08:58:46

2017-04-05 16:40:45

2015-05-25 15:31:56

C語(yǔ)言學(xué)習(xí)和使用 C 語(yǔ)言

2009-06-06 19:32:49

2011-06-09 10:14:47

2017-11-14 11:12:50

Go語(yǔ)言編譯器

2021-01-26 05:37:08

分庫(kù)分表內(nèi)存

2014-05-30 15:56:26

iOS 8WWDC2014

2021-05-17 08:20:22

職場(chǎng)晉升轉(zhuǎn)型

2020-12-21 14:28:01

語(yǔ)言JavaC ++

2018-05-23 00:20:29

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)