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


      失落迷茫了好一段日子。终于我用接触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你已经完全看不懂了对不对?

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

  • 相关阅读:
    Jquery弹出框插件大全
    RGB颜色在线转换
    正则表达式最后加一个/g或者/ig代表什么意思
    JS实现页面上链接新窗口打开
    防止网站服务器被黑的一些方法
    JS中字符串背后的秘密
    ASP.NET MVC 路由规则写法
    日积月累从细节做起
    VC++ 配置困惑
    父类子类指针函数调用注意事项
  • 原文地址:https://www.cnblogs.com/heron/p/3762692.html
Copyright © 2020-2023  润新知