Chapter 3Managing Function Inputs.md

Managing Function Inputs

all of one

多元转一元

funtion 描述

1
2
3
4
5
function unary (fn) {
return function onlyOneArg(arg) {
return fn(arg)
}
}

arrow function 描述

1
const unary = fn => arg => fn(arg)

explames

1
2
3
4
5
[1, 2, 3].map(parseInt); // [1, na, na]
// 解释一下为什么, parseInt 接收两个参数
// parseInt(a, b)
// a 要转换成整形的值
// b 要转换成数值的形式(二进制, 八进制, 十进制)

one by one

怎么说捏, 这个函数, 用在延迟计算上真的很有用,比如我需要一个值, 这个值在我下一步函数用到,但是这个值, 不能污染我下一个函数啊, 那么我就可以使用组合的方式, 使用这个identity函数去组合.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// function
function identity (v) {
return v;
}
// arrow function
const identity = v => v;


// explames
// 1\. 空值检测
var words = " Now is the time for all... ".split( /\s|\b/ );

words.filter( identity );

// 2\. 参数为函数时, 作为该参数的默认值, 去掉原逻辑中,判断该参数是否函数的默认值
function output(msg,formatFn = identity) {
msg = formatFn( msg );
console.log( msg );
}

function upper(txt) {
return txt.toUpperCase();
}

output( "Hello World", upper ); // HELLO WORLD
output( "Hello World" ); // Hello World

unchanging

某些API不让你直接传入promise, 而是要通过一个返回一个函数来调用的方式, 比如在promise链, then函数中使用另一个promise函数, 这个时候就用到了

1
2
3
4
5
6
7
8
// don't work:
p1.then(foo).then(p2).then(bar)

// instead:
p1.then(foo).then(function () {return p2}).then(bar)

// or use arrow function
p1.then(foo).then(() => p2).then(bar)

FP中有个很好的工具解决这个问题

1
2
3
4
5
6
7
8
9
10
11
12
// function 
function constant (v) {
return function value () {
return v;
}
}

// arrow funtion
const constant = v => () => v;

// so you can use constant function
p1.then(foo).then(constant(p2)).then(bar)

Adapting Arguments to Parameters

先看一个例子

1
2
3
4
5
6
7
function foo(x, y) {
return x + y
}

fucntion bar (fn) {
fn([3, 9])
}

我知道, 我们可以在fn([3, 9])使用解构去处理数组, 但是对于FPer来说, 我们需要更强的可读性, 所以有了下面的小玩具

1
2
3
4
5
6
7
function spreadArgs(fn) {
return function speadFn(argsArr) {
return fn(...argsArr)
}
}

const spreadArgs = fn => argsArr => fn(...argsArr)

但是有时候 , 我们又需要相反的操作.所以出现了下面的另一个小操作

1
2
3
4
5
6
7
fucntion gatherArgs(fn) {
return function gatheredFn(...argsArr) {
return fn(argsArr)
}
}

cosnt gatherArgs = fn => (...argsArr) => fn(argsArr)

Some Now, Some Later —- 偏函数

先看个例子

1
2
3
4
5
6
7
8
9
10
11
function getPerson(data, cb) {
Ajax('http://api/person', data, cb)
}
function getPerson(data, cb) {
Ajax('http://api/person', data, cb)
}

// 这个时候, 我又需要一个定制的接口, 用于获取特定用户的信息
function getCurrentUser (cb) {
getPerson({user: CURRENT_USER_ID}, cb)
}

以上的操作大家都不陌生, 就对于基本函数ajax的一个增强, 比如写死他的url, 写死, 请求方式形成get/post特定的请求方式.但是对于函数式编程来说, 这些都归为偏函数, 让我们来看偏函数这个玩具.

1
2
3
4
5
6
7
function partial (fn, ...presetArgs) {
return function partiallyApplied(...lastArgs) {
return fn(...presetArgs, ...lastArgs)
}
}

const partial = (fn, ...presetArgs) => (...lastArgs) => fn(...presetArgs, ...lastArgs)

好啦, 本质上就是一个闭包的使用,真的超好用. 但是, 对于js本身自带的API里其实就有类似偏函数的功能, 比如bind, 但是呢, bind存在问题,就是硬绑定this, 如果你不需要this, 那么就要传一个null作为一个占位符.你不觉得很很多余吗?

Reversing Arguments

属于部分应用函数的增强.偏函数的传入顺序是左到右, 而reversing的意思就是把传入顺序改为右到左.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function reverseArgs (fn) {
return function argsReversed(...args) {
fn(args.reverse())
}
}

const reverseArgs = (fn) => (...args) => fn(args.reverse())

// 另一种partialRight的技巧
function partialRight (fn, presetArgs) {
return function partiallyApplied (...laterArgs) {
return fn(...laterArgs, ...presetArgs)
}
}

const partialRight = (fn, ...presetArgs) => (...laterArgs) => fn(...later, ...presetArgs)

One at a time

额, 柯里化函数, 很像偏函数, 把需要多个参数的函数变成一系列连续调用的单参数函数.也就类似于, 每个函数都接受一个参数, 并且返回一个函数来接受下一个参数。

来看个场景

1
2
3
4
5
6
curriedAjax('http://some.api/person')({user: CURRENT_USER_ID})(function() { console.log('成功后的回调')})
// 或许以上的代码有点难以理解, 让我们把它拆分成
const personFetcher = curriedAjax('http://some.api/person');
var getCurrentUser = personFetcher({user: CURRENT_USER_ID});
getCurrentUser(function foundUser(user){// 成功后的回调})
// 这两种代码其实都是一个意思, 但是对于FP来说, 第一种风格就是我们提倡使用的

所以让我们来看一下curry玩具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function curry(fn, arity = fn.length) {
return (function nextCurried(prevArgs) {
return function curried(nextArg) {
var args = [...prevArgs, nextArg];

if(args.length >= arity) {
return fn(...args);
}else {
return nextCurried(args);
}
}
})([])
}
const curry = (fn, arity = fn.length, nextCurried) => (nextCurried = prevArgs => nextArg => {
var args = [...prevArgs, nextArg]

if(args.length >= arity) {
return fn(...args)
}else {
return nextCurried(args)
}
})([])

回到一开始的例子, 我们用curry来解决我们遇到的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
const curriedAjax = curry(ajax)
const personFetcher = curriedAjax('http:some/api/person')

const getCurrentUser = personFetcher({user: CURRENT_USER_ID})

getCurrentUser(function callback () {
// do something
})

// 再看一下之前的例子
const add = (a, b) => a + b

[1, 2, 3].map(curry(add)(3)) // 此时add函数中的a为3

柯里化和偏函数的区别, 偏函数是固定某些参数, 而达到增强。而偏函数的目的是适应变化.

Visualizing Curried Functions

来让我们手动的去定义一个curried函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
funtion curriedSum (v1) {
return function(v2) {
return function (v3) {
return function (v4) {
return function (v5) {
return sum (v1, v2, v3, v4, v5)
}
}
}
}
}
// 用箭头函数
const curriedSum = v1 => v2 => v3 => v4 => v5 => sum(v1, v2, v3, v4, v5)

Why Currying and Partial Application?

类比于普通函数, sum(1, 2, 3, 4, 5), 为什么要选择, 柯里化sum(1)(2)(3)这种形式, 或者使用偏函数partial(sum, 1, 2)这种形式呢?

  1. 你可以在某一时刻去指定部分参数, 然后在另一时刻再去指定剩余参数. 而传统的函数则要求所有参数都必须同时存在(传入).
  2. 当只有一个参数时, 函数组合将会更高效, 更容易.一元函数将会更易使用.
  3. 其实柯里化和偏函数增加了代码的可读性,同时使代码更为简洁.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ajax(
'http://some.api/person',
{user: CURRENT_USER_ID},
function foundUser(data) {

}
)
// 让我们使用偏函数去优化它
const getCurrentUser = partial(
ajax,
'http://some.api/person',
{user: CURRENT_USER_ID}
);
getCurrentUser(function foundUser() {
// do something
})

LooseCurry

宽松的curry指的是, 我可以一次传入多个参数,而不是像之前curry每次只传入一个, 来看个实例

1
2
3
4
5
6
// curry
sum(1)(2)(3)


// looseCurry
sum(1, 2)(3)

下面让我们看看looseCurry的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function looseCurry(fn, arity = fn.length) {
return (function nextCurried(pervArgs){
return function curried(...nextArgs) {
var args = [...pervArgs, ...nextArgs]

if(args.length >= arity) {
return fn(...args)
}else {
return nextCurried(args)
}
}
})([])
}

const looseCurry = (fn, arity = fn.length, nextCurried = pervArgs => (...nextArgs) => {
var args = [...pervArgs, ...nextArgs]

if(args.length > arity) {
return fn(...args)
}else {
return nextCurried(args)
}
})([])

No curry

如果你现在有个curry化函数, 你想把它转回正常函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function uncurry(fn) {
return function uncurried (...args) {
var ret = fn

for(let arg in args) {
ret = ret(arg)
}

return ret
}
}

// =>
const uncurry = fn => (...args) => {
var ret = fn

for(let arg in args) {
ret = ret(arg)
}

return ret
}

用命名空间来优化偏函数和柯里化函数, 不受顺序的影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function partialProps(fn, presetArgsObj) {
return function partiallyApplied(laterArgsObj) {
return fn(Object.assign({}, presetArgsObj, laterArgsObj))
}
}

function curryProps(fn,arity = 1) {
return (function nextCurried(prevArgsObj){
return function curried(nextArgObj = {}){
var [key] = Object.keys( nextArgObj );
var allArgsObj = Object.assign(
{}, prevArgsObj, { [key]: nextArgObj[key] }
);

if (Object.keys( allArgsObj ).length >= arity) {
return fn( allArgsObj );
}
else {
return nextCurried( allArgsObj );
}
};
})( {} );
}

来看个使用例子

1
2
3
4
5
6
7
8
9
10
11
12
function foo({ x, y, z } = {}) {
console.log( `x:${x} y:${y} z:${z}` );
}

var f1 = curryProps( foo, 3 );
var f2 = partialProps( foo, { y: 2 } );

f1( {y: 2} )( {x: 1} )( {z: 3} );
// x:1 y:2 z:3

f2( { z: 3, x: 1 } );
// x:1 y:2 z:3