前言:

why

  • 1.困惑:

1.mutable state(可变状态)
2.unrestricted side effects(无限制副作用)
3.unprincipled design(无原则设计)

  • 2.规则:

1.DRY(don’t repeat yourself)(不要重复你自己)
2.LCHC(loose coupling high cohesion)(高内聚、低耦合
3.YAGNI(you aren’t gonna need it)(你不会用到它的)
4.POLS(Principle of least surprise)(最小意外原则)
5.SR(single responsibility)(单一原则)

  • 3.定义:

范畴论(需要注意是【函子】)->一整套函数的运算方法->最开始用于数学计算->后来在计算机编程实现(函数式编程)

1.一等公民

  • 1.使用一个函数把另外一个函数包裹起来,仅仅是为了延迟执行函数,是个糟糕的编程习惯
// 太傻了
var getServerStuff = function(callback){
  return ajaxCall(function(json){
    return callback(json);
  });
};

// 这才像样
var getServerStuff = ajaxCall;

1.1.提高维护成本、以及增加检索
1.2.函数变动,包裹函数也要发生变化

// bad
httpGet('/post/2', function(json){
  return renderPost(json);
});
httpGet('/post/2', function(json, err){
  return renderPost(json, err);
});
// good
httpGet('/post/2', renderPost);
  • 1.3.命名,通用代码使用通用规则,业务代码使用业务命名
// 只针对当前的博客
var validArticles = function(articles) {
  return articles.filter(function(article){
    return article !== null && article !== undefined;
  });
};

// 对未来的项目友好太多
var compact = function(xs) {
  return xs.filter(function(x) {
    return x !== null && x !== undefined;
  });
};
  • 1.4.避免使用this
// 好一些 
fs.readFile('freaky_friday.txt', Db.save.bind(Db));
  • 1.4.1.箭头函数中最好不使用this
$('.pay-btn').on('click', () => {
	let $this = $(this);
	console.log('this document element was: ', $this[0]);
});
  • 1.4.2.vue中使用this的话,建议如下
init() {
	let self = this;
	self.loadList().then(resp => {
		return resp.data || [];
	})
	.then(datas => {
		self.datas = datas;
		return null;
	})
	.then(() => {
		setTimeout(() => {
			// 此处的self是vue中的this,指向创建的vue对象本身
			self.lazyLoadImages();			
		}, 0);
	});
},
lazyLoadImages() {
	// 图片懒加载
}
  • 1.4.2.1.【QS】vue中this总是指向vue创建的对象本身,这个是怎么实现的?

2.纯函数

  • 定义: 不改变输入值(如果值是地址引用的话)
var arr = [13, 12, 33, 44];
// start: 下标, end: 第几个(下标+1)
// 纯函数
arr.slice(0, 1); 
// start: 下标, howmany: 多少个
// 函数不纯,改变了arr
arr.splice(3, 1); 
  • 定义:不依赖外部变量
// 不纯的
var minimum = 21;
var checkAge = function(age) {
  return age >= minimum;
};

// 纯的
var checkAge = function(age) {
  var minimum = 21;
  return age >= minimum;
};

依赖是增加系统复杂度的罪魁祸首

  • 副作用:计算过程中,系统状态一种变化/与外界进行可观察的交互

1.尽量避免副作用的(后面可以使用functor,monad使之可控,如果不可避免)解释:
死水中的水不是滋生病菌的培养器,”死“才是原因。副作用的“副”是滋生bug的温床

  • 原则: 「相同输入得到相同输出」
var upper = function(str) {
	if (str.constructor !== String) {
		throw new Error(`paramter(${str}) was not a string`);
	}
	return str.toUpperCase();
}

1.纯函数是数学上的函数,是函数式编程的全部

  • 推荐理由:
  1. 可缓存(cacheable)
// 缓存函数,而非其结果
var memoize = function(f) {
  var cache = {};

  return function() {
    var arg_str = JSON.stringify(arguments);
    cache[arg_str] = cache[arg_str] || f.apply(f, arguments);
    return cache[arg_str];
  };
};
var squareNumber  = memoize(function(x){ return x*x; });
squareNumber(4);
  1. 可移植性/自文档化(Portable / Self-Documenting)
// 纯的:参数指明当前函数依赖
var signUp = function(Db, Email, attrs) {
  return function() {
    var user = saveUser(Db, attrs);
    welcomeUser(Email, user);
  };
};

var saveUser = function(Db, attrs) {
    ...
};

var welcomeUser = function(Email, user) {
    ...
};
  1. 可测试性(Testable): quick check /quick check(jianshu)
  2. 并行执行代码: 纯函数不需要访问共享内存,不会因为副作用进入竞态(race condition)/竞态(csdn)

3.柯里化(curry)

  • 前言: 有些事物你得到之前无足轻重,得到之后就变得不可或缺。

  • 概念: 只给函数一部分参数来调用它,让它返回一个函数处理剩下的参数(把多参数函数转换成单参数函数)

// update before
var add = function(x, y){
	return x + y;
}
add(12, 13);
// update after
var add = function(x) {
  return function(y) {
    return x + y;
  };
};

var increment = add(1);
var addTen = add(10);
increment(2);
addTen(2);
// or use like this
add(1)(2)

  • 实际运用
var curry = require('lodash').curry;

var match = curry(function(what, str) {
  return str.match(what);
});
match(/\s+/g, "hello world");
  • curry实现
// 第一版
var curry = function (fn) {
	// 取出除fn剩余参数
    var args = [].slice.call(arguments, 1);
    return function() {
        var newArgs = args.concat([].slice.call(arguments));
        return fn.apply(this, newArgs);
    };
};

1.ramda和underscore、loadash的区别是参数位置不一样
2.ramda推崇的理念: function first, data last

1.参数复用: 有默认参数和一堆可选参数实现一个

// 下为官员如何搞定7个老婆的测试
// 获得合法老婆
var getWife = currying(function() {
    var allWife = [].slice.call(arguments);
    // allwife 就是所有的老婆的,包括暗渡陈仓进来的老婆
    console.log(allWife.join(";"));
}, "合法老婆");

// 获得其他6个老婆
getWife("大老婆","小老婆","俏老婆","刁蛮老婆","乖老婆","送上门老婆");

// 换一批老婆
getWife("超越韦小宝的老婆");

2.提前返回

var addEvent = (function(){
    if (window.addEventListener) {
        return function(el, sType, fn, capture) {
            el.addEventListener(sType, function(e) {
                fn.call(el, e);
            }, (capture));
        };
    } else if (window.attachEvent) {
        return function(el, sType, fn, capture) {
            el.attachEvent("on" + sType, function(e) {
                fn.call(el, e);
            });
        };
    }
})();

3.延迟执行/运算

var curryWeight = function(fn) {
    var _fishWeight = [];
    return function() {
        if (arguments.length === 0) {
        	// 最终执行函数 
            return fn.apply(null, _fishWeight);
        } else {
        	// 只是累加参数
            _fishWeight = _fishWeight.concat([].slice.call(arguments));
        }
    }
};
var fishWeight = 0;
var addWeight = curryWeight(function() {
    var i=0; len = arguments.length;
    for (i; i<len; i+=1) {
        fishWeight += arguments[i];
    }
});

addWeight(2.3);
addWeight(6.5);
addWeight(1.2);
addWeight(2.5);
addWeight();    //  这里才计算

console.log(fishWeight);    // 12.5

4.代码组合(compose)

  • 普通的函数组合
var compose = function(f,g) {
  return function(x) {
    return f(g(x));
  };
};
// 变大写
var toUpperCase = function(x) { return x.toUpperCase(); };
// 最后加一个感叹号
var exclaim = function(x) { return x + '!'; };
var shout = compose(exclaim, toUpperCase);

shout("send in the clowns");

5.示例应用

  • 声明式和命令式区别:
  1. 命令式代码: 一步、一步的指示
var cars = [{
	name: 'JiPu',
	make: 'China'
}, {
	name: 'DaZhong',
	make: 'Germany'
}]
var makes = [];
for (i = 0; i < cars.length; i++) {
  makes.push(cars[i].make);
}
console.log(makes);
  1. 声明式代码: 表达式
var cars = [{
	name: 'JiPu',
	make: 'China'
}, {
	name: 'DaZhong',
	make: 'Germany'
}]
var getMakes = _.map(_.prop('make'));
var makes = getMakes(cars);
console.log(makes);
  • 等式推导(equational reasoning)和纯函数
// 原有代码
var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
var srcs = _.compose(_.map(mediaUrl), _.prop('items'));
var images = _.compose(_.map(img), srcs);
// 优化第一遍(少一行代码)
var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
var images = _.compose(_.map(img), _.map(mediaUrl), _.prop('item
s'));
// 优化第二遍(只有一次循环)
var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
var images = _.compose(_.map(_.compose(img, mediaUrl)), _.prop('
items'));

ps: 1.base function

var img = function (url) {
      return $('<img />', { src: url });
};

ps: 2.组合定律

var law = compose(map(f), map(g)) == map(compose(f, g))

下篇:异常处理,代码分支,函数式整个应用,让应用更富有表现力?


6.Hindley-Milner类型签名

  • 1.释义: js是一种动态类型语言

1.类型签名:暴露函数的行为和目的

js中类型检查工具:flow,静态方言:typescript

2.函数签名:
2.1.string=>string

//  capitalize :: String -> String
var capitalize = function(s){
  return toUpperCase(head(s)) + toLowerCase(tail(s));
}
capitalize("smurf");
//=> "Smurf"

2.2.string=>number

//  strLength :: String -> Number
var strLength = function(s){
  return s.length;
}

2.3.更复杂一点的签名

//  match :: Regex -> (String -> [String])
var match = curry(function(reg, s){
  return s.match(reg);
});
//  usage:
//  onHoliday :: String -> [String]
var onHoliday = match(/holiday/ig);
  • 2.作用: compile time check(编译时检测), best document, 解决函数式编程/隐式编程,参数被弱化带来的阅读和使用困难

  • 3.衍生: free theorems(自由定理)

// head :: [a] -> a
compose(f, head) == compose(head, map(f));
// filter :: (a -> Bool) -> [a] -> [a]
compose(map(f), filter(compose(p, f))) == compose(filter(p), map
(f));
  • 4.范围: 精确度很高; 抽象、通用
// reduce::(b->a->b)->b->[a]->b
var reduce = curry(function(f, x, xs){
  return xs.reduce(f, x);
});
// id::a->a
var id = function(x){ return x; }
  • 5.parametricity: 函数 将会以一种统一的行为作用于所有的类型
// head :: [a] -> a
  • 6.类型约束(type constraints)
// sort :: Ord a => [a] -> [a]

// typescript
constructor(title: string, priceCode: string) {
    this.title = title;
    this.priceCode = priceCode;
}
// vue
props: {
    data: {
        default: {},
        type: Object
    }
}

7.特百惠

  • 1.容器
// define
var Container = function(x) {
  this.__value = x;
}
Container.of = function(x) { 
	return new Container(x); 
};
// usage
Container.of(3)
  • 2.functor: 实现了 map 函数并遵守一些特定规则的容器类型
// (a -> b) -> Container a -> Container b
Container.prototype.map = function(f){
  return Container.of(f(this.__value))
}
// usage
Container.of("bombs").map(_.concat(' away')).map(_.prop('length'))
//=> Container(10)
  • 3.Maybe: 用于那些无法成功返回结果的函数中; 和container区别、避免空值
var Maybe = function(x) {
  this.__value = x;
}
Maybe.of = function(x) {
  return new Maybe(x);
}
Maybe.prototype.isNothing = function() {
  return (this.__value === null || this.__value === undefined);
}
Maybe.prototype.map = function(f) {
  return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value)); 
}

// usage
Maybe.of("Malkovich Malkovich").map(match(/a/ig));
//=> Maybe(['a', 'a'])
Maybe.of(null).map(match(/a/ig));
//=> Maybe(null)
  • 4.“纯”错误处理: Either容器, 合法错误检查, 文件丢失, socket短连, 表示了一种逻辑或(   ), 体现了范畴学里 coproduct 的概念, 标准的sum-types
// left: 返回错误的值
var Left = function(x) {
    this.__value = x;
};
Left.of = function(x) {
    return new Left(x);
};
Left.prototype.map = function(f) {
    return this;
};
// right: 返回正确的值
var Right = function(x) {
    this.__value = x;
};
Right.of = function(x) {
    return new Right(x);
};
Right.prototype.map = function(f) {
    return Right.of(f(this.__value));
};
// usage:
var moment = require('moment');
//  getAge :: Date -> User -> Either(String, Number)
var getAge = curry(function(now, user) {
    var birthdate = moment(user.birthdate, 'YYYY-MM-DD');
    if(!birthdate.isValid())
        return Left.of("Birth date could not be parsed");
    return Right.of(now.diff(birthdate, 'years'));
});
getAge(moment(), {birthdate: '2005-12-12'});
// Right(9)
getAge(moment(), {birthdate: 'balloons!'});
// Left("Birth date could not be parsed")
  • 5.io: 把非纯动作捕获包括到函数中,延时执行这个动作
var IO = function(f) {
    this.__value = f;
};
IO.of = function(x) {
    return new IO(function() {
        return x;
    });
};
IO.prototype.map = function(f) {
    return new IO(_.compose(f, this.__value));
};

ps: 应该被调用者以最公开的方式调用

var IO = function(f) {
	this.unsafePerformIO = f;
}
IO.prototype.map = function(f) {
	return new IO(_.compose(f, this.unsafePerformIO));
}
// usage
findParam("searchTerm").unsafePerformIO()
  • 6.异步任务
var fs = require('fs');
var Task = require('data.task');
var _ = require('ramda');

var readfile = function (filename) {
    return new Task(function (reject, resolve) {
        fs.readFile(filename, 'utf-8', function (error, data) {
            if (error) {
                return reject(error);
            }
            resolve(data);
        });
    });
}

// readfile usage
console.log('read file result: ');
var readResult = readfile('./readdata.txt').map(_.split('\n')).map(_.head);
readResult.fork(
    function (error) {
        console.log('error: ', error);
    },
    function (data) {
        console.log('data: ', data);
    }
);
  • promise不是一个纯函数?

8.Monad

  • pointed functor是实现了of的functor
  • monad是实现了of和join的functor

9.Applicative functor

ps:参考资料