首页 > 代码库 > 函数式编程第一步——流程控制

函数式编程第一步——流程控制

  失落迷茫了好一段日子。终于我用接触2个月的技术Nodejs成功的混到一份工作。严格来说只学习了3天(白天睡觉,晚上通宵学习),后面的时间都是在配置环境。总的来说,函数式编程是有应用的市场的,而且学习门槛也不是太高。就算从来没听说过函数式编程的人也会知道javascript,也会使用jquery。虽然很多是把它当作过程式的来用,来看待。这也是在于它的语法看起来太像C,太像过程式的语言。

  之前一直想写一些关于函数编程文章来记录我学习的历程。之前写了一篇使用F#的,不过大家好像对F#比较排斥。以后我从工作出发写nodejs的吧。

  好了。废话不多说我们先从一个具体的项目来分析函数式编程吧。

  用webstorm新建一个express项目,这是nodejs下用来做web服务器的库。会生成类似下面这个结构的文件。

  • /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服务器,加上各种奇葩的中间件的运用,会让项目变成各种形态。这是一个自由度非常高的开发平台。

   我们先写一个简单的demo。由于js的语法太过纠结,我们使用另外一种语言coffeescript,他是一个nodejs的库。能自己运行在nodejs上,也能编译成js文件。这里我们只是用做语法糖,仍然编译成js文件。我会贴出两种代码来适应不同的需要。

coffeescript

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

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

fna()
fnb()

javscript

// 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会严格申明变量和闭包,不会让其污染全局变量。代码精简不少,看起来也更像是函数式编程了。输入结果显而易见。

console.log

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你已经完全看不懂了对不对?

  今天就写到这里了,我接触到的范围也不广,以后大家有什么关于函数式编程的问题可以告知,大家一起解决。