函數式編程第一步——流程控制

  失落迷茫了好一段日子。終於我用接觸2個月的技術Nodejs成功的混到一份工做。嚴格來講只學習了3天(白天睡覺,晚上通宵學習),後面的時間都是在配置環境。總的來講,函數式編程是有應用的市場的,並且學習門檻也不是過高。就算曆來沒據說過函數式編程的人也會知道javascript,也會使用jquery。雖然不少是把它看成過程式的來用,來看待。這也是在於它的語法看起來太像C,太像過程式的語言。javascript

  以前一直想寫一些關於函數編程文章來記錄我學習的歷程。以前寫了一篇使用F#的,不過你們好像對F#比較排斥。之後我從工做出發寫nodejs的吧。css

  好了。廢話很少說咱們先從一個具體的項目來分析函數式編程吧。前端

  用webstorm新建一個express項目,這是nodejs下用來作web服務器的庫。會生成相似下面這個結構的文件。java

  • /bin/www : 項目的啓動文件,配置了監聽的端口,固然程序入口仍是app.js
  • /node_modules/ : 經過npm包管理中間件都在這,包括session,模板,日誌等中間件,你本身安裝的中間件也在這
  • /public/  : 暴露的文件夾,從名字就能夠看出,圖面前端js腳本和css會在這裏
  • /routes/ : 路由,至關於控制器
  • /views/ : 模板文件
  • /app.js : 約定俗成的項目入口
  • /package.json : 配置你項目依賴的包,使用npm命令 npm install -d 會自動安裝裏面記錄的中間件,很是方便。因爲nodejs的中間件不徹底是腳本組成的,也會包含C寫的編譯文件,各環境下不盡相同,因此經過npm,本地下載編譯是很是重要的
 

  總的來講文件結構只是約定俗成,或是按人們習慣來用的。不像java、C之類的會有main函數做爲入口。任何文件都能看成啓動入口。nodejs也不只限於開發web服務器,加上各類奇葩的中間件的運用,會讓項目變成各類形態。這是一個自由度很是高的開發平臺。node

   咱們先寫一個簡單的demo。因爲js的語法太過糾結,咱們使用另一種語言coffeescript,他是一個nodejs的庫。能本身運行在nodejs上,也能編譯成js文件。這裏咱們只是用作語法糖,仍然編譯成js文件。我會貼出兩種代碼來適應不一樣的須要。jquery

coffeescriptgit

fna = ->
  console.log("I am 'a'")

fnb = ->
  console.log "I am 'b'"

fna()
fnb()

javscriptgithub

// Generated by CoffeeScript 1.7.1
(function() {
  var fna, fnb;

  fna = function() {
    return console.log("I am 'a'");
  };

  fnb = function() {
    return console.log("I am 'b'");
  };

  fna();

  fnb();

}).call(this);

//# sourceMappingURL=test.map

  這裏我編寫了兩個函數,並依次調用它們。coffeescript會嚴格申明變量和閉包,不會讓其污染全局變量。代碼精簡很多,看起來也更像是函數式編程了。輸入結果顯而易見。web

console.logexpress

i am 'a'
i am 'b'

  nodejs是異步執行的。若是這是兩個有關聯的函數呢?

coffeescript

fna = ->
  console.log("這是母雞")

fnb = ->
  console.log "母雞下蛋"

fna()
fnb()

javascript

// Generated by CoffeeScript 1.7.1
(function() {
  var fna, fnb;

  fna = function() {
    return console.log("這是母雞");
  };

  fnb = function() {
    return console.log("母雞下蛋");
  };

  fna();

  fnb();

}).call(this);

//# sourceMappingURL=test.map

  單從結果來看,好像沒有什麼問題。

console.log

這是母雞
母雞下蛋

  在實際項目中,咱們並不知道兩個函數內部到底幹了什麼,就像蝴蝶效應,任何改動均可能讓結果發生變更。

coffeescript

fna = ->
  setTimeout ->
    console.log("這是母雞")
  , 100

fnb = ->
  console.log "母雞下蛋"

fna()
fnb()

javascript

// Generated by CoffeeScript 1.7.1
(function() {
  var fna, fnb;

  fna = function() {
    return setTimeout(function() {
      return console.log("這是母雞");
    }, 100);
  };

  fnb = function() {
    return console.log("母雞下蛋");
  };

  fna();

  fnb();

}).call(this);

//# sourceMappingURL=test.map

console.log

母雞下蛋
這是母雞

  如今就不是咱們想要的結果了。其實這種異步方式也很好理解,它只管函數調用,而無論函數結果。在同步編程中,前一步操做會阻塞後一步操做,母雞下蛋的操做會等着這隻母雞出結果。而異步編程中,不會阻塞後面的任務進行,就像指揮官給手下發派任務,手下都會去執行各自的任務,但何時完成任務就很差說了。這樣作的好處就是在執行耗時任務的時候,其餘的任務也能繼續執行,或者同時執行多個耗時任務。可是有利有弊,在流程控制上會比較糾結。常規作法是用回調函數,就像有人說過,世上原本沒有回調,用的人多了也就有了回調函數。

coffeescript

fna = (next) ->
  setTimeout ->
    console.log("這是母雞")
    next()
  , 1000

fnb = ->
  console.log "母雞下蛋"

fna ->
  fnb()

javascript

// Generated by CoffeeScript 1.7.1
(function() {
  var fna, fnb;

  fna = function(next) {
    return setTimeout(function() {
      console.log("這是母雞");
      return next();
    }, 1000);
  };

  fnb = function() {
    return console.log("母雞下蛋");
  };

  fna(function() {
    return fnb();
  });

}).call(this);

//# sourceMappingURL=test.map

conslole.log

這是母雞
母雞下蛋

  這中方法雖然解決了關聯函數的流程控制問題,可是也有新的問題。邏輯複雜的時候,回調嵌套就會愈來愈深。

coffeescript

fna = (next) ->
  setTimeout ->
    console.log("這是母雞")
    next()
  , 1000

fnb = (next) ->
  setTimeout ->
    console.log "母雞下蛋"
    next()
  , 100

fnc = ->
  console.log "蛋孵出了雞"

fna ->
  fnb ->
    fnc()

javascript

// Generated by CoffeeScript 1.7.1
(function() {
  var fna, fnb, fnc;

  fna = function(next) {
    return setTimeout(function() {
      console.log("這是母雞");
      return next();
    }, 1000);
  };

  fnb = function(next) {
    return setTimeout(function() {
      console.log("母雞下蛋");
      return next();
    }, 100);
  };

  fnc = function() {
    return console.log("蛋孵出了雞");
  };

  fna(function() {
    return fnb(function() {
      return fnc();
    });
  });

}).call(this);

//# sourceMappingURL=test.map

console.log

這是母雞
母雞下蛋
蛋孵出了雞

  幸虧有中間件解決這個問題。async 中間件有各類流程控制方法。其中series就能很優美的實現這個邏輯。你所要作的就是每一個函數里加上一個回調next執行下一步操做,第一個參數是err,第二個參數能追加一個結果,在async最後的回調中返回出來。

coffeescript

async = require "async"
fna = (next) ->
  setTimeout ->
    console.log "這是母雞"
    next(null, 1)
  , 1000

fnb = (next) ->
  setTimeout ->
    console.log "母雞下蛋"
    next(null, 2)
  , 2000

fnc = (next) ->
  setTimeout ->
    console.log "蛋孵出了雞"
    next(null, 3)
  , 100


async.series [
  fna
  fnb
  fnc
]
,  (err, results) ->
   console.log results

javascript

// Generated by CoffeeScript 1.7.1
(function() {
  var async, fna, fnb, fnc;

  async = require("async");

  fna = function(next) {
    return setTimeout(function() {
      console.log("這是母雞");
      return next(null, 1);
    }, 1000);
  };

  fnb = function(next) {
    return setTimeout(function() {
      console.log("母雞下蛋");
      return next(null, 2);
    }, 2000);
  };

  fnc = function(next) {
    return setTimeout(function() {
      console.log("蛋孵出了雞");
      return next(null, 3);
    }, 100);
  };

  async.series([fna, fnb, fnc], function(err, results) {
    return console.log(results);
  });

}).call(this);

//# sourceMappingURL=test.map

console.log

這是母雞
母雞下蛋
蛋孵出了雞
[ 1, 2, 3 ]

 


 

更好的封裝,應該是這個樣子。

coffeescript

async = require "async"
fna = (next) ->
  setTimeout ->
    console.log "這是母雞"
    next()
  , 1000

fnb = (next) ->
  setTimeout ->
    console.log "母雞下蛋"
    next()
  , 2000

fnc = (next) ->
  setTimeout ->
    console.log "蛋孵出了雞"
    next()
  , 100


async.series [
  (next) ->
    fna ->
      next null, 1
  (next) ->
    fnb ->
      next null, 2
  (next) ->
    fnc ->
      next null, 3
]
,  (err, results) ->
   console.log results

javascript

// Generated by CoffeeScript 1.7.1
(function() {
  var async, fna, fnb, fnc;

  async = require("async");

  fna = function(next) {
    return setTimeout(function() {
      console.log("這是母雞");
      return next();
    }, 1000);
  };

  fnb = function(next) {
    return setTimeout(function() {
      console.log("母雞下蛋");
      return next();
    }, 2000);
  };

  fnc = function(next) {
    return setTimeout(function() {
      console.log("蛋孵出了雞");
      return next();
    }, 100);
  };

  async.series([
    function(next) {
      return fna(function() {
        return next(null, 1);
      });
    }, function(next) {
      return fnb(function() {
        return next(null, 2);
      });
    }, function(next) {
      return fnc(function() {
        return next(null, 3);
      });
    }
  ], function(err, results) {
    return console.log(results);
  });

}).call(this);

//# sourceMappingURL=test.map

到這裏coffeescript還能夠一戰,js你已經徹底看不懂了對不對?

  今天就寫到這裏了,我接觸到的範圍也不廣,之後你們有什麼關於函數式編程的問題能夠告知,你們一塊兒解決。

相關文章
相關標籤/搜索