函数式编程
前言:
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.纯函数是数学上的函数,是函数式编程的全部
- 推荐理由:
- 可缓存(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);
- 可移植性/自文档化(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) {
...
};
- 可测试性(Testable): quick check /quick check(jianshu)
- 并行执行代码: 纯函数不需要访问共享内存,不会因为副作用进入竞态(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);
};
};
- 帮助curry的库: ramda,loadash-fp
- slice:
arr.slice([begin[, end]])
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.示例应用
- 声明式和命令式区别:
- 命令式代码: 一步、一步的指示
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);
- 声明式代码: 表达式
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