1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        如何成為一名函數(shù)式程序員(I)

        共 5156字,需瀏覽 11分鐘

         ·

        2016-11-17 19:00

        file
        理解函數(shù)式編程概念是最重要,有時也是最難的一步。

        學(xué)開車

        file

        在第一次學(xué)開車的時候,掙扎是難免的??粗鴦e人開起來很簡單,但實際上比我們想象的要難一些。在爸媽的車里練習(xí),直到對小區(qū)的路都熟悉了才敢開到路上去。在經(jīng)過反復(fù)的練習(xí)以及一些恐慌的時刻之后,我們終于學(xué)會了開車,拿到了駕照。

        拿到駕照之后,我們會抓住所有的機會開車出去。一次一次,越來越嫻熟,我們也越來越自信。有一天我們不得不借別人的車出去,或者我們的車徹底壞了,不得不買一輛新的。這時候問題就來了,開一輛新車是怎樣的感覺?會和第一次開車的感覺一樣嗎?

        這兩種感覺差老遠了。

        第一次開車的時候,完全是陌生的感覺。雖然在此之前,我們坐過車,但都是以乘客的身份。開車就不一樣了,我們是坐在駕駛位子上,控制車里的各種東西。

        在開第二輛車的時候,我們只是會問一些簡單的問題,比如鑰匙去哪了?燈在哪里?你怎么使用轉(zhuǎn)向燈,怎么調(diào)側(cè)視鏡?

        之后就非常順利了。但是為什么這次就這么簡單呢?

        這是因為新車和舊車很相像,基本的東西都一樣,幾乎在同樣的位置。

        有些東西安裝的不一樣,可能增添了額外的特性(feature),在第一次甚至第二次開的時候都不會用到。最后我們也都了解了這些新特性,至少了解了我們關(guān)心的那些。

        學(xué)習(xí)編程語言就有點像學(xué)車。第一次最難,但是一旦你學(xué)會了一種,之后的就簡單了。

        當你開始學(xué)習(xí)第二種語言的時候,你會問一些這樣問題,比如“我如何創(chuàng)建一個模塊?你怎么搜索數(shù)組?substring 函數(shù)的參數(shù)是多少?”

        在學(xué)習(xí)新語言的時候,你會變得自信,因為你會想到之前的語言,加了一些新的東西,學(xué)起來也容易些。

        你的第一艘宇宙飛船

        file



        不管你這輩子是只開過一輛車還是開了幾十輛車,現(xiàn)在想象一下你要駕駛一艘宇宙飛船的情景。

        如果你要駕駛飛船的話,你肯定不會期望曾經(jīng)開車的能力能夠幫到你。你得從零開始。(作為程序員,我們都是從0開始計數(shù)。)

        開始訓(xùn)練的時候,你就已預(yù)想到在太空中是完全不一樣的,雖然物理空間不變,還是在同一宇宙中,但是駕駛飛船和開車完全是兩碼事。

        這和學(xué)習(xí)函數(shù)式編程是類似的。編程就是在思考,函數(shù)式編程會教你如何以不同的方式思考。以至于,你之后再也沒法回到以前的思考方式。

        將自己歸零

        file

        人們很喜歡說這句話,將自己歸零,這么說是有些道理的。學(xué)習(xí)函數(shù)式編程就像是從頭開始。不完全,但很貼切。如果你是希望一切都從頭學(xué)起的話,那是最好的。

        看問題的角度正確了,才會有正確的預(yù)期;預(yù)期正確了,才不會在遇到難題的時候輕易放棄。

        作為一名程序員,有各種各樣的事情,你已成習(xí)慣了,但是在函數(shù)式編程的時候是沒法做的。

        就像在車里,你習(xí)慣從私人車道倒出來。但是在飛船里,根本就沒有倒擋?,F(xiàn)在你可能會想,“什么?沒倒擋?!沒倒擋,我TM怎么開?!”

        沒倒擋就說明在飛船里根本不需要倒擋,因為飛船可以在三維空間里任人操作。一旦你理解了這點之后,你就不會再想著倒擋了。事實上,某一天,你會覺得車這東西真的是限制太多了。

        學(xué)習(xí)函數(shù)式編程需要一段時間,要有耐心。

        走出命令式編程的冰冷世界,溫柔地沉浸到函數(shù)式編程的暖春中。

        在開始研究函數(shù)語言之前,下面的函數(shù)式編程概念對你會有所幫助。如果你已經(jīng)開始嘗試了,下面的編程概念會讓你有更全面的理解。

        請花點時間向下讀,理解一下編碼示例。 最重要的就是你要理解它。

        純潔性

        file

        當函數(shù)型程序員說起純度 純潔性的時候,他們指的是純函數(shù)。純函數(shù)是非常簡單的函數(shù),只運行輸入的參數(shù)。

        舉一個用Javascript寫的純函數(shù)例子:

        var z = 10;
        function add(x, y) {
            return x + y;
        }

        要注意的是add函數(shù)不會觸發(fā)z變量,不會從z開始讀取,也不會寫到z,只會讀取xy(輸入值),返回加和結(jié)果。

        這就是純函數(shù)。如果add函數(shù)訪問到z,那么它就不是純函數(shù)了。

        下面是另一個函數(shù):

        function justTen() {
            return 10;
        }

        如果函數(shù)just Ten是純函數(shù)的話,那它只能返回一個常數(shù)。為什么?

        因為我們還沒輸入任何東西。如果要變成一個純函數(shù)的話,是沒法訪問到輸入值以外的任何值的,所以唯一能返回的值就是個常數(shù)。

        沒有參數(shù)的純函數(shù)無法運行,所以沒太大用處。如果just Ten定義為一個常數(shù),那就更好了。

        大多數(shù)有用的純函數(shù)都必須至少有一個參數(shù)。

        看看這個函數(shù):

        function addNoReturn(x, y) {
            var z = x + y
        }

        這個函數(shù)不能返回任何值。添加xy,得出變量z,但是不會返回任何值。

        這是個純函數(shù),因為它只處理輸入值。但是輸入了也沒有返回任何結(jié)果,所以這函數(shù)是無效的。

        所有有效的純函數(shù)都必須返回一些值才行。

        我們再看看第一個add函數(shù):

        function add(x, y) {
            return x + y;
        }
        console.log(add(1, 2)); // prints 3
        console.log(add(1, 2)); // still prints 3
        console.log(add(1, 2)); // WILL ALWAYS print 3

        我們看到add(1,2)總是返回3。這并不奇怪,因為這個函數(shù)是純函數(shù)。如果add函數(shù)使用外部的值,那么就沒法預(yù)知結(jié)果了。

        同樣的輸入,純函數(shù)得出的結(jié)果都是一樣的。

        因為純函數(shù)是沒法改變?nèi)魏瓮獠孔兞康模韵旅孢@些函數(shù)都不是純函數(shù):

        writeFile(fileName);
        updateDatabaseTable(sqlCmd);
        sendAjaxRequest(ajaxRequest);
        openSocket(ipAddress);

        這些函數(shù)都有副作用,當你調(diào)用的時候,文件和數(shù)據(jù)表都會改變,數(shù)據(jù)發(fā)送到服務(wù)器,或者調(diào)用OS套接,不僅僅是運行輸入值返回輸出值。所以永遠也沒法預(yù)判這些函數(shù)會返回什么東西。

        純函數(shù)沒有副作用。

        在命令式編程語言中,比如Javascript、Java和C#,副作用無處不在。這使得調(diào)試非常困難,因為在項目中一個變量隨處都可改變。所以當發(fā)現(xiàn)一個bug,這bug是因為一個變量在錯誤的時間被改成了錯誤的數(shù)值導(dǎo)致的,怎么找出來?到處找嗎?根本不行!

        這時候,你可能會在想 “只有個純函數(shù),我TM怎么辦?”

        在函數(shù)式編程中,你不僅僅是寫純函數(shù)。

        函數(shù)式編程無法徹底消除副作用,只能限定。因為各個項目要和真實的世界交互,每一個項目中某一部分必須是非純函數(shù)的。目標是最小化非純碼的數(shù)量,將其與其它的項目隔離開。

        不變性

        你還記得你第一次看到下面這些代碼的時候嗎?

        var x = 1;
        x = x + 1;

        是不是每個教你的人都告誡你忘記你在數(shù)學(xué)課上學(xué)的東西?在數(shù)學(xué)上,x是不可能等于x+1的。

        但是在命令式編程中,x+1的意思是將x現(xiàn)在的值加上1,然后將值返回給x。

        在函數(shù)式編程中,x = x + 1是非法的。所以你必須得記住你忘記的那一點點數(shù)學(xué)知識。

        函數(shù)式編程中不存在變量。

        存儲的值也叫作變量,但是他們都是常數(shù),比如一旦x取了一個值,那它永遠就是這個值。

        不用擔(dān)心,x通常是個局部變量,所以它的生命通常很短暫。但是在生命期中,它的值是無法改變的。

        下面是EIm中的一個固定變量的例子,EIm是一種用于網(wǎng)頁開發(fā)的純函數(shù)式編程語言。

        addOneToSum y z =
            let
                x = 1
            in
                x + y + z

        如果你對ML-Style語法不熟悉的話,我來解釋一下,addOneToSum是一個有兩個參數(shù)(yz)的函數(shù)。

        let模塊內(nèi),x綁定值為1,也就是它的值等于1。在函數(shù)退出或者更精確的說是當let模塊求值之后,x的生命就結(jié)束了。

        in模塊,計算包括let模塊定義的數(shù)值,也就是x。返回x + y + z的計算結(jié)果,更精確地說是,因為x = 1,返回1 + y + z這樣一個結(jié)果。

        你肯定會又問“沒有變量,我TM到底該怎么做?!”

        我們來想想什么時候要調(diào)整變量。一般有兩種情況:多值變化(比如:改變某個對象或記錄的一個值)和單值變化(比如:循環(huán)計數(shù)器)。

        函數(shù)式編程通過利用數(shù)據(jù)結(jié)構(gòu)復(fù)制值變化后的記錄(不用復(fù)制全部記錄)的方式高效地處理記錄中數(shù)值的變化。

        函數(shù)式編程也是通過同樣地方式(即復(fù)制)解決單值變化。

        是的,但是沒有循環(huán)。

        你肯定又抓狂了:“什么?沒變量,現(xiàn)在又沒循環(huán)?!”

        冷靜,不是不能進行循環(huán),只是沒有特定的循環(huán)結(jié)構(gòu),像for, while, do, repeat等。

        函數(shù)式編程使用遞歸循環(huán)

        下面是使用Javascript語言寫循環(huán)的兩種方式:

        // simple loop construct
        var acc = 0;
        for (var i = 1; i <= 10; ++i)
            acc += i;
        console.log(acc); // prints 55
         // without loop construct or variables (recursion)
        function sumRange(start, end, acc) {
             if (start > end)
                 return acc;
             return sumRange(start + 1, end, acc + start)
        }
         console.log(sumRange(1, 10, 0)); // prints 55

        我們看看遞歸(一種函數(shù)方法)是怎樣通過自我調(diào)用一個new開始(start + 1) 和一個new累加器(acc + start)實現(xiàn)和 for循環(huán)一樣的結(jié)果的。遞歸不需要改變原先的值,而是使用舊值算出的新值。

        不幸的是,即使你花了一點時間學(xué)習(xí),在Javascript中也很難看到這個。有兩個原因:一是Javascript語法比較雜亂;二是你可能不習(xí)慣用遞歸的方式思考。

        在EIm中,讀取更容易,理解也相應(yīng)的更容易:

        sumRange start end acc =
            if start > end then  
                acc
            else
                sumRange (start + 1) end (acc + start) 

        下面是運行過程:

        sumRange 1 10 0 =      -- sumRange (1 + 1)  10 (0 + 1)
        sumRange 2 10 1 =      -- sumRange (2 + 1)  10 (1 + 2)
        sumRange 3 10 3 =      -- sumRange (3 + 1)  10 (3 + 3)
        sumRange 4 10 6 =      -- sumRange (4 + 1)  10 (6 + 4)
        sumRange 5 10 10 =     -- sumRange (5 + 1)  10 (10 + 5)
        sumRange 6 10 15 =     -- sumRange (6 + 1)  10 (15 + 6)
        sumRange 7 10 21 =     -- sumRange (7 + 1)  10 (21 + 7)
        sumRange 8 10 28 =     -- sumRange (8 + 1)  10 (28 + 8)
        sumRange 9 10 36 =     -- sumRange (9 + 1)  10 (36 + 9)
        sumRange 10 10 45 =    -- sumRange (10 + 1) 10 (45 + 10)
        sumRange 11 10 55 =    -- 11 > 10 => 55
        55

        你可能會覺得for循環(huán)更容易理解。然而,這是有爭議的,更可能是熟悉度的問題,非遞歸循環(huán)需要可變性,可變性這就糟糕了。

        這里我沒有完全解釋不變性的好處,你可以到另一篇文章《為什么程序員需要限定》(Why Programmers Need Limits)中的Global Mutable State查看一下,會學(xué)到更多。

        不變性一個明顯的好處就是如果你訪問了項目中的一個值,你只能讀取訪問,這意味著其他沒有人能改變這個值,即使是你。所以不會發(fā)生意外的變性。

        另外,如果你的項目是多線程的,那么其它線程沒法讓你掛掉的。值是不變的,如果另一個線程要改變它,必須從舊的值里創(chuàng)建一個新值。

        在20世紀90年代中期,我為生物危機(Creature Crunch)寫了個游戲引擎,最大的bug來源就是多線程問題。我很希望能夠重新了解不變性。但是我更在意是2x或4x速率CD-ROM驅(qū)動對游戲性能有啥區(qū)別。

        不變性創(chuàng)建了更簡單安全的代碼。

        file

        這篇翻譯起來感覺有些難,所以以上不一定準確。有什么地方不對的,麻煩各位大神在評論中指出來,請多多指教!
        瀏覽 177
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            日韩在线视频一区二区三区 | wwwav在线播放 | 国产猛烈尖叫高潮视频免费 | 成人一级黄色毛片 | 国产成人精品午夜2025 | 久久爱伊人 | 自慰在线 | 下载国产一级黄色片 | 三级视频在线播放 | 淫香淫色插插插 |