函数式编程笔记


函数式编程笔记

柯里化示例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var adder = function () {
var _args = [];
return function () {
if (arguments.length === 0) {
return _args.reduce(function (a, b) {
return a + b;
});
}
[].push.apply(_args, [].slice.call(arguments));
return arguments.callee;
}
};
var sum = adder();
console.log(sum); // Function
sum(100,200)(300); // 调用形式灵活,一次调用可输入一个或者多个参数,并且支持链式调用
sum(400);
console.log(sum()); // 1000 (加总计算)

柯里化示例2 currying

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var currying = function (fn) {
var _args = [];
return function () {
if (arguments.length === 0) {
return fn.apply(this, _args);
}
Array.prototype.push.apply(_args, [].slice.call(arguments));
return arguments.callee;
}
};

var multi=function () {
var total = 0;
for (var i = 0, c; c = arguments[i++];) {
total += c;
}
return total;
};

var sum = currying(multi);

sum(100,200)(300);
sum(400);
console.log(sum()); // 1000 (空白调用时才真正计算)

柯里化示例3

柯里化的基础
上面的代码(示例2)其实是一个高阶函数(high-order function), 高阶函数是指操作函数的函数,它接收一个或者多个函数作为参数,并返回一个新函数。此外,还依赖与闭包的特性,来保存中间过程中输入的参数。即:
函数可以作为参数传递
函数能够作为函数的返回值
闭包
柯里化的作用

1
2
3
4
5
6
7
8
9
10
11
var addEvent = function(el, type, fn, capture) {
if (window.addEventListener) {
el.addEventListener(type, function(e) {
fn.call(el, e);
}, capture);
} else if (window.attachEvent) {
el.attachEvent("on" + type, function(e) {
fn.call(el, e);
});
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
});
};
}
})();

Function.prototype.bind 方法也是柯里化应用
与 call/apply 方法直接执行不同,bind 方法 将第一个参数设置为函数执行的上下文,其他参数依次传递给调用方法(函数的主体本身不执行,可以看成是延迟执行),并动态创建返回一个新的函数, 这符合柯里化特点。

1
2
3
4
5
var foo = {x: 888};
var bar = function () {
console.log(this.x);
}.bind(foo); // 绑定
bar(); // 888

下面是一个 bind 函数的模拟,testBind 创建并返回新的函数,在新的函数中将真正要执行业务的函数绑定到实参传入的上下文,延迟执行了。

1
2
3
4
5
6
7
8
9
Function.prototype.testBind = function (scope) {
var fn = this; //// this 指向的是调用 testBind 方法的一个函数,
return function () {
return fn.apply(scope);
}
};
var testBindBar = bar.testBind(foo); // 绑定 foo,延迟执行
console.log(testBindBar); // Function (可见,bind之后返回的是一个延迟执行的新函数)
testBindBar(); // 888

这里要注意 prototype 中 this 的理解。

组合 compose

示例1

1
2
3
4
5
var compose = function(f,g) {
return function(x) {
return f(g(x));
};
};
1
2
3
4
var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = compose(exclaim, toUpperCase);
shout("send in the clowns");
1
2
3
var shout = function(x){
return exclaim(toUpperCase(x));
};

示例2

1
2
3
4
5
6
var head = function(x) { return x[0]; };
var reverse = reduce(function(acc, x){ return [x].concat(acc); }, []);
var last = compose(head, reverse);

last(['jumpkick', 'roundhouse', 'uppercut']);
//=> 'uppercut'

** head ** 取第一个元素,** reverse **反转元素序列

1
2
3
// 结合律(associativity)
var associative = compose(f, compose(g, h)) == compose(compose(f, g), h);
// true

compose 的调用分组不重要,所以结果都是一样的

1
2
3
4
compose(toUpperCase, compose(head, reverse));

// 或者
compose(compose(toUpperCase, head), reverse);
1
2
3
4
5
6
7
8
9
10
11
12
// 前面的例子中我们必须要写两个组合才行,但既然组合是符合结合律的,我们就可以只写一个,
// 而且想传给它多少个函数就传给它多少个,然后让它自己决定如何分组。

var lastUpper = compose(toUpperCase, head, reverse);

lastUpper(['jumpkick', 'roundhouse', 'uppercut']);
//=> 'UPPERCUT'

var loudLastUpper = compose(exclaim, toUpperCase, head, reverse)

loudLastUpper(['jumpkick', 'roundhouse', 'uppercut']);
//=> 'UPPERCUT!'
1
2
3
4
5
6
7
8
9
10
11
12
var loudLastUpper = compose(exclaim, toUpperCase, head, reverse);

// 或
var last = compose(head, reverse);
var loudLastUpper = compose(exclaim, toUpperCase, last);

// 或
var last = compose(head, reverse);
var angry = compose(exclaim, toUpperCase);
var loudLastUpper = compose(angry, last);

// 更多变种...

pointerfree

1
2
3
4
5
6
7
// 非 pointfree,因为提到了数据:word
var snakeCase = function (word) {
return word.toLowerCase().replace(/\s+/ig, '_');
};

// pointfree
var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);

示例

1
2
3
4
5
6
7
8
9
// 非 pointfree,因为提到了数据:name
var initials = function (name) {
return name.split(' ').map(compose(toUpperCase, head)).join('. ');
};

// pointfree
var initials = compose(join('. '), map(compose(toUpperCase, head)), split(' '));

initials("hunter stockton thompson");

debug

组合的一个常见错误是,在没有局部调用之前,就组合类似 map 这样接受两个参数的函数。

1
2
3
4
5
6
7
8
9
10
11
// 错误做法:我们传给了 `angry` 一个数组,根本不知道最后传给 `map` 的是什么东西。
var latin = compose(map, angry, reverse);

latin(["frog", "eyes"]);
// error

// 正确做法:每个函数都接受一个实际参数。
var latin = compose(map(angry), reverse);

latin(["frog", "eyes"]);
// ["EYES!", "FROG!"])

http://www.2cto.com/kf/201412/357997.html

https://www.gitbook.com/book/llh911001/mostly-adequate-guide-chinese/details


文章作者: HKmaster
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 HKmaster !
评论
  目录