失落迷茫了好一段日子。终于我用接触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("这是母鸡")
, 100fnb = ->
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()
, 1000fnb = ->
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()
, 1000fnb = (next) ->
setTimeout ->
console.log "母鸡下蛋"
next()
, 100fnc = ->
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)
, 1000fnb = (next) ->
setTimeout ->
console.log "母鸡下蛋"
next(null, 2)
, 2000fnc = (next) ->
setTimeout ->
console.log "蛋孵出了鸡"
next(null, 3)
, 100async.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()
, 1000fnb = (next) ->
setTimeout ->
console.log "母鸡下蛋"
next()
, 2000fnc = (next) ->
setTimeout ->
console.log "蛋孵出了鸡"
next()
, 100async.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你已经完全看不懂了对不对?
今天就写到这里了,我接触到的范围也不广,以后大家有什么关于函数式编程的问题可以告知,大家一起解决。