Home  >  Article  >  Web Front-end  >  Detailed introduction to some thoughts on JavaScript function currying

Detailed introduction to some thoughts on JavaScript function currying

黄舟
黄舟Original
2017-03-08 14:20:031252browse

1. Pitfalls of higher-order functions

Before learning currying, let’s first look at the following piece of code:

var f1 = function(x){
    return f(x);
};
f1(x);

Many students can see this , these are very silly to write, because the functions f1 and f are equivalent, we can directly make var f1 = f;, there is no need to wrap it at all So one layer.

However, you may not be able to see the problem with the following piece of code:

var getServerStuff = function(callback){
  return ajaxCall(function(json){
    return callback(json);
  });
};

This is a piece of code I excerpted from the "JS Functional Programming Guide". In fact, using the above Rules, we can conclude that callback is equivalent to function

function(json){return callback(json);};

, so the function can be simplified to:

var getServerStuff = function(callback){
  return ajaxCall(callback);
};

Continue to simplify:

var getServerStuff = ajaxCall;

In this way, we found that such a long program was written in vain.

Function can be used as both a parameter and a return value. It is an important feature of high-order functions, but it is easy to step into a pit if you are not careful.

2. Function Curry

Getting to the point, what is function curry? Function curry is to call a function by passing only part of its parameters and have it return a function to handle the remaining parameters. It sounds confusing, but it is actually very simple. In fact, it is to split the variables of the function and call it: f(x,y,z) -> f(x)(y)(z).

For the initial example, implement it as follows. Two parameters need to be passed in. f1The calling method is f1(f,x).

var f1 = function(f,x){
    return f(x);
};

Note that since f is passed in as a function variable, f1 becomes a new function.

We will change f1, using closure can be written in the following form, then the f1 calling method becomes f1( f)(x), and get exactly the same result. This completes the currying of f1.

var f1 = function(f){
    return function(x){
        return f(x);
    }
};
var f2 = f1(f);
f2(x);

In fact, this example is inappropriate. Careful students may find that although f1 is a new function, f2 and f They are completely equivalent. After detouring for a long time, they still came back.

Here is a very classic example:

['11', '11', '11'].map(parseInt) //[ 11, NaN, 3 ]
['11', '11', '11'].map(f1(parseInt)) //[ 11, 11, 11 ]

Since parseInt accepts two parameters, there will be problems with hexadecimal conversion when calling directly. Please refer to "Unwilling to Convert "Leave" article.

var f2 = f1(parseInt), f2 changes parseInt from accepting two parameters to only accepting one parameter New function to solve this hexadecimal conversion problem. After passing our f1 package, we will be able to run the correct results.

Some students feel that this is not a curried application. I think it should be counted. All students can discuss it together.

3. Further thoughts on function currying

In the example in the previous section, instead of running f(x) directly, we Function f is used as a parameter, what will be the result? Let’s look at the following example:

Assume that f1 returns function g, the scope of g points to xs, functionf is used as a parameter of g. Finally, we can write it in the following form:

var f1 = function(f,xs){
    return g.call(xs,f);
};

In fact, using f1 to replace g.call(xxx) is called anti-currying. For example:

var forEach = function(xs,f){
    return Array.prototype.forEach.call(xs,f);
};
var f = function(x){console.log(x);};
var xs = {0:'peng',1:'chen',length:2};
forEach(xs,f);

Anti-curring is to defer the original fixed parameters or this context as parameters to the future.
It can simplify functions to a great extent, provided you get used to it.

Putting aside anti-currying, what if we wanted to curry f1?

Using closure, we can write it in the following form:

var f1 = function(f){
    return function(xs){
        return g.call(xs,f);
    }
};
var f2 = f1(f);
f2(xs);

Pass f into f1, we can get f2This new function.

Only passing a part of the parameters to the function is usually called a partial application, which can greatly reduce boilerplate code.

Of course, the two parameters passed in to function f1 do not necessarily have to include function + non-function. They may both be functions, or both may be non-functions.

I personally feel that currying is not necessary, and unfamiliar students may have trouble reading it, but it can help us understand functional programming in JS, and more importantly, we will You won't feel unfamiliar when reading similar code. Luo Chen on Zhihu said it very well:

It is not that "currying" is meaningful for functional programming. Rather, while functional programming treats functions as first-class citizens, it will inevitably lead to the usage of "currying". So it's not like it just "makes sense". Of course, since it exists, we can naturally explore how to use this phenomenon.

Exercise:

// 通过局部调用(partial apply)移除所有参数
var filterQs = function(xs) {
  return filter(function(x){ return match(/q/i, x);  }, xs);
};
//这两个函数原题没有,是我自己加的
var filter = function(f,xs){
    return xs.filter(f);
};
var match = function(what,x){
    return x.match(what);
};

Analysis: The function of function filterQs is to pass in a string array, filter out the string containing 'q', and Form a new array and return it.

We can get the function filterQs through the following steps:

a. filter传入的两个参数,第一个是回调函数,第二个是数组,filter主要功能是根据回调函数过滤数组。我们首先将filter函数柯里化:

var filter = function(f){
    return function (xs) {
        return xs.filter(f);
    }
};

b. 其次,filter函数传入的回调函数是matchmatch的主要功能是判断每个字符串是否匹配what这个正则表达式。这里我们将match也柯里化:

var match = function(what){
    return function(x){
        return x.match(what);
    }
};
var match2 = match(/q/i);

创建匹配函数match2,检查字符串中是否包含字母q。

c. 把match2传入filter中,组合在一起,就形成了一个新的函数:

var filterQs =  filter(match2);
var xs = ['q','test1','test2'];
filterQs(xs);

从这个示例中我们也可以体会到函数柯里化的强大。所以,柯里化还有一个重要的功能:封装不同功能的函数,利用已有的函数组成新的函数。

4. 函数柯里化的递归调用

函数柯里化还有一种有趣的形式,就是函数可以在闭包中调用自己,类似于函数递归调用。如下所示:

function add( seed ) {
    function retVal( later ) {
        return add( seed + later );
    }
    retVal.toString = function() {
        return seed;
    };
    return retVal;
}
console.log(add(1)(2)(3).toString()); // 6

add函数返回闭包retVal,在retVal中又继续调用add,最终我们可以写成add(1)(2)(3)(...)这样柯里化的形式。
关于这段代码的解答,知乎上的李宏训同学回答地很好:

每调用一次add函数,都会返回retValue函数;调用retValue函数会调用add函数,然后还是返回retValue函数,所以调用add的结果一定是返回一个retValue函数。add函数的存在意义只是为了提供闭包,这个类似的递归调用每次调用add都会生成一个新的闭包。

5. 函数组合(compose)

函数组合是在柯里化基础上完成的:

var compose = function(f,g) {
  return function(x) {
    return f(g(x));
  };
};
var f1 = compose(f,g);
f1(x);

将传入的函数变成两个,通过组合的方式返回一个新的函数,让代码从右向左运行,而不是从内向外运行。

函数组合和柯里化有一个好处就是pointfree。

pointfree 模式指的是,永远不必说出你的数据。它的意思是说,函数无须提及将要操作的数据是什么样的。一等公民的函数、柯里化(curry)以及组合协作起来非常有助于实现这种模式。

// 非 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");
// 'H. S. T'

The above is the detailed content of Detailed introduction to some thoughts on JavaScript function currying. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn