阮一峰老师的函数式编程入门教程:http://www.ruanyifeng.com/blog/2017/02/fp-tutorial.html
Franklin Risby 教授的函数式编程指北:https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch1.html
关于什么是函数式编程,就不多说什么了,给两个大神的链接给各位朋友瞅瞅。以下记录以下函数式编程中重要的知识点
函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。(MDN对于闭包的定义https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures)
通过一个只执行一次的函数的例子,了解一下闭包的使用方式
function once(){
let done = false;
return function(){
if(!done){
console.log(done);
done = true;
}
}
}
let f = once();
f()
f()
...
上面这个函数,无论调用多少次,只有打印第一次。 f引用的是once内部的函数。在外面,我们通过调用f可以访问到once函数的作用域。
function memorize(fn){
let caches = {} // 用于缓存之前的计算
return function(){
let arg_str = JSON.stringify(arguments)
caches[arg_str] = caches[arg_str] || fn.apply(null,arguments);
return caches[arg_str]
}
}
function sum(a ,b){
console.log(a,b); // 从这里可以看出执行了几次sum函数
return a + b;
}
let sumM = memorize(sum)
console.log(sumM(1,2));
console.log(sumM(2,2));
console.log(sumM(1,2));
function curry(fn){
return function curried(...args){
// args还用来保存上一步的参数
if(fn.length > args.length){
return function(){
return curried(...args.concat(Array.from(arguments)))
}
}
return fn(...args)
}
}
function add(a, b, c){
return a + b + c
}
let cAdd = curry(add)
console.log(cAdd(1)(2)(3));
console.log(cAdd(1,2)(3));
console.log(cAdd(1,2,3));
console.log(cAdd(1)(2,3));
如图,现在有这么一个操作,数据a经过f函数处理后在经过g函数处理后得到c;代码操作入下
function f(x){
return x + 1
}
function g(x){
return x * x
}
console.log(g(f(2)));
如果增加一些操作就会形如以下a(b(c(d(e())))); 为了处理这样的函数,就需要组合一下函数了,使我们最后能够使用f(x)就能得到结果
function compose(...args){
return function(x){
return args.reduce(function(total,fn){
return fn(total)
},x)
}
}
let p = console(f,g)
简化compose
let compose = (...args) => x => args.reduce((total,fn) => fn(total), x);
有些副作用是不可避免的,但是使用函子,可以将副作用控制在可控范围内。
函数副作用是指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。副作用的函数不仅仅只是返回了一个值,而且还做了其他的事情。这里有一边关于副作用的文章:http://www.fly63.com/article/detial/1176
副作用如下
1、修改了一个变量
2、直接修改数据结构
3、设置一个对象的成员
4、抛出一个异常或以一个错误终止
5、打印到终端或读取用户输入
6、读取或写入一个文件
7、在屏幕上画图
class Functor {
// 为了使用这个函子的时候可以不在外部显示的使用new functor,添加一个静态的of方法
static of(value){
return new Functor(value)
}
constructor(value){
this._value = value
}
map(fn){
return Functor.of(fn(this._value))
}
getVal(){
return this._value
}
}
let p = Functor.of(2).map(x => x + 2).map(x => x * 2).getVal()
console.log(p);
// 由于传入为空,不能执行转为大写操作,报错
Functor.of(null).map(x => x.toUpperCase())
class MayBe extends Functor{
static of(value){
return new MayBe(value)
}
map(fn){
return this._value ? Functor.of(fn(this._value)) : Functor.of(null)
}
}
let p2 = MayBe.of(null).map(x => x.toUpperCase()).getVal()
console.log(p2);
Either 并不仅仅只对合法性检查这种一般性的错误作用非凡,对一些更严重的、能够中断程序执行的错误比如文件丢失或者 socket 连接断开等,Either 同样效果显著。这里,我仅仅是把 Either 当作一个错误消息的容器介绍给你!
class Left{
static of(value){
return new Left(value)
}
constructor(value){
this._value = value
}
map(fn){
return this
}
}
class Right{
static of(value){
return new Right(value)
}
constructor(value){
this._value = value
}
map(fn){
return Right.of(fn(this._value))
}
}
function parseJSON(str){
try{
return Right.of(JSON.parse(str))
}catch(err){
return Left.of({message: err.message})
}
}
let p = parseJSON('hello world')
console.log(p);
const fp = require('lodash/fp')
class IO{
static of(value){
return new IO(function(){
return value
})
}
constructor(fn){
this._value = fn
}
map(fn){
return new IO(fp.flowRight(fn,this._value))
}
}
let f = new IO(process).map(p => p.execPath)
// npm i folktale
// Task 处理异步任务
const fs = require('fs')
const {task} = require('folktale/concurrency/task')
const {split, find} = require('lodash/fp')
function readFile(filename){
return task(resolver => {
fs.readFile(filename,'utf-8',(err,data) => {
if(err)resolver.reject(err)
resolver.resolve(data)
})
})
}
readFile('package.json') // 返回task函子
.map(split('\n'))
.map(find(x => x.includes('version')))
.run()
.listen({
onRejected: err => {
console.log(err);
},
onResolved: value => {
console.log(value);
}
})
函子嵌套了
const fs = require('fs')
const fp = require('lodash/fp')
class IO{
static of(value){
return new IO(function(){
return value
})
}
constructor(fn){
this._value = fn
}
map(fn){
return new IO(fp.flowRight(fn,this._value))
}
}
let readFile = function(filename){
return new IO(function(){
return fs.readFileSync(filename,'utf-8')
})
}
let Print = function(x){
return new IO(function(){
console.log(x);
return x
})
}
let cat = fp.flowRight(Print, readFile)
let r = cat('package.json')
console.log(r._value()._value());
解决函子嵌套的问题
const fs = require('fs')
const fp = require('lodash/fp')
class IO{
static of(value){
return new IO(function(){
return value
})
}
constructor(fn){
this._value = fn
}
map(fn){
return new IO(fp.flowRight(fn,this._value))
}
join(){
return this._value()
}
flatMap(fn){
let s = this.map(fn).join()
console.log(1,s);
return s
}
}
let readFile = function(filename){
return new IO(function(){
let file = fs.readFileSync(filename,'utf-8')
console.log(file);
return file
})
}
let Print = function(x){
console.log("flatMap中join:执行读取,并且执行打印,结束后就是读取完数据,并且返回打印中那个函子")
console.log(x);
return new IO(function(){
return x
})
}
let cat = readFile('package.json')
.flatMap(Print)
console.log(cat);