In-depth analysis of Currying in JavaScript Functions-javascript tutorial

Source: Internet
Author: User
Tags csv parser sendmsg ocaml
This article mainly introduces the Currying of functions in JavaScript. The significance of Currying is that it can completely convert the function into a fixed form of & quot; accepting a parameter and returning a value & quot, for more information, see Introduction
Let's look at a small problem:
Someone has a question in the group:
Var s = sum (1) (2) (3) ...... finally alert (s) is 6
Var s = sum (1) (2) (3) (4) ...... finally alert (s) is 10
How to Implement sum?
When I saw the question, my first response was that sum returned a function, but it was not implemented. I was impressed with the similar principle, but I cannot remember it.

Later, my colleagues said that this was called colihua,
The implementation method is clever:

function sum(x){  var y = function(x){   return sum(x+y)  }  y.toString = y.valueOf = function(){   return x;  }  return y; } 

Next, let's take a closer look at currying kerihua ~

What is kerizhua?

Curialization is a conversion process that converts a function that accepts multiple parameters into a function that accepts a single parameter (note: the first parameter of the original function, if other parameters are necessary, return the new function that accepts the remaining parameters and returns the result.

When we say this, I think ke Lihua sounds quite simple. How is JavaScript implemented?
Suppose we want to write a function and accept three parameters.

var sendMsg = function (from, to, msg) { alert(["Hello " + to + ",", msg, "Sincerely,", "- " + from].join("\n"));};

Now, let's assume that we have a colized function that can convert a traditional JavaScript function into a colized function:

var sendMsgCurried = curry(sendMsg); // returns function(a,b,c) var sendMsgFromJohnToBob = sendMsgCurried("John")("Bob"); // returns function(c) sendMsgFromJohnToBob("Come join the curry party!"); //=> "Hello Bob, Come join the curry party! Sincerely, - John"

Manual kerriization

In the above example, we assume we have a mysterious curry function. I will implement such a function, but now, let's first look at why such a function is so necessary.
For example, it is not difficult to manually commercialize a function, but it is a bit cool:

// uncurriedvar example1 = function (a, b, c) { // do something with a, b, and c}; // curriedvar example2 = function(a) { return function (b) {  return function (c) {   // do something with a, b, and c  }; };};

In JavaScript, a function is called even if you do not specify all the parameters of a function. This is a very practical JavaScript function, but it creates trouble for colihua.

The idea is that every function has only one parameter. If you want to have multiple parameters, you must define a series of nested functions. Annoying! This can be done twice, but when you need to define a function that requires many parameters in this way, it will become quite awkward and difficult to read. (But don't worry, I will tell you a way immediately)

Some Function programming languages, such as Haskell and OCaml, have built-in function Keri. In these languages, for example, each function has one parameter and only one parameter. You may think that this restriction is better than the advantage, but the syntax of the language is like this, and this restriction is almost imperceptible.

For example, in OCaml, you can define the preceding example in two ways:

let example1 = fun a b c -> // (* do something with a, b, c *) let example2 = fun a -> fun b ->  fun c ->   // (* do something with a, b, c *)

It is easy to see how these two examples are similar to the above two examples.

Difference, however, whether or not the same thing is done in OCaml. OCaml, no function with multiple parameters. However, declaring multiple parameters in a row is the "shortcut" of the nested single-Parameter Function ".

Similarly, we expect that the syntax of calling a colized function is similar to that of calling a multi-parameter function in OCaml. We expect to call the above function as follows:

example1 foo bar bazexample2 foo bar baz

In JavaScript, we adopt a significantly different method:

example1(foo, bar, baz);example2(foo)(bar)(baz);

In languages such as OCaml, colialization is built-in. In JavaScript, curialization is feasible (high-order functions), but it is not convenient in syntax. This is why we decided to write a colized function to help us do these tedious tasks and make our code concise.

Create a curry helper Function

Theoretically, we expect a convenient way to convert regular JavaScript Functions (multiple parameters) to fully colized functions.

This idea is not exclusive to me. Others have already implemented it, for example, the. autoCurry () function in the wu. js Library (although you are concerned with our own implementation method ).

First, let's create a simple helper function. sub_curry:

function sub_curry(fn /*, variable number of args */) { var args = [].slice.call(arguments, 1); return function () {  return fn.apply(this, args.concat(toArray(arguments))); };}

Let's take a moment to look at the functions of this function. It is quite simple. Sub_curry accepts a function fn as its first parameter, followed by any number of input parameters. A function is returned. This function returns the fn. apply execution result. The parameter sequence merges the initial input parameters of the function, and the input parameters when fn is called.

Example:

var fn = function(a, b, c) { return [a, b, c]; }; // these are all equivalentfn("a", "b", "c");sub_curry(fn, "a")("b", "c");sub_curry(fn, "a", "b")("c");sub_curry(fn, "a", "b", "c")();//=> ["a", "b", "c"]

Obviously, this is not what I want, but it seems a bit of a ke Lihua. Now we will define the curry function:

function curry(fn, length) { // capture fn's # of parameters length = length || fn.length; return function () {  if (arguments.length < length) {   // not all arguments have been specified. Curry once more.   var combined = [fn].concat(toArray(arguments));   return length - arguments.length > 0     ? curry(sub_curry.apply(this, combined), length - arguments.length)    : sub_curry.call(this, combined );  } else {   // all arguments have been specified, actually call function   return fn.apply(this, arguments);  } };}

This function accepts two parameters: one function and the number of parameters to be "colized. The second parameter is optional. If omitted, the Function. prototype. length attribute is used by default to tell you how many parameters have been defined for this Function.

In the end, we can demonstrate the following actions:

var fn = curry(function(a, b, c) { return [a, b, c]; }); // these are all equivalentfn("a", "b", "c");fn("a", "b", "c");fn("a", "b")("c");fn("a")("b", "c");fn("a")("b")("c");//=> ["a", "b", "c"]

I know what you're thinking...

Wait... What ?!

Are you crazy? This should be the case! Now we can write colized functions in JavaScript, just like those in OCaml or Haskell. Even if I want to pass multiple parameters at a time, I can separate them with commas as I previously did. There is no need for ugly parentheses between parameters, even after colized.

This is quite useful. I will talk about it right away, but first I want to take a small step forward for this Curry function.

Colihua and "holes" ("holes ")

Although the Keri function is awesome, it also requires you to focus on the parameter sequence of the function you define. After all, the idea behind keri is to create functions, more specific functions, separate other more common functions, and apply them step by step.

Of course, this can only work when the leftmost parameter is the parameter you want to apply step by step!

To solve this problem, a special placeholder variable is defined in some functional programming languages ". It is usually used to specify an underscore to do this. If it is passed in as a function parameter, it indicates that it can be skipped ". Is to be specified.

This is very useful. When you want to apply a specific function step by step (partially apply), but the parameter that you want to apply the distributed application (partially apply) is not the leftmost parameter.

For example, we have a function:

var sendAjax = function (url, data, options) { /* ... */ }

Maybe we want to define a new function. We provide some Options specific to the SendAjax function, but allow url and data to be specified.

Of course, we can define functions as follows:

var sendPost = function (url, data) { return sendAjax(url, data, { type: "POST", contentType: "application/json" });};

Alternatively, use the agreed underline method, as shown below:

var sendPost = sendAjax( _ , _ , { type: "POST", contentType: "application/json" });

Note that the two parameters are passed in as follows. Obviously, JavaScript does not have such native support, so how can we do this?

Let's turn curry functions into intelligence...

First, we define our placeholder as a global variable.

var _ = {};

We define it as the object literal quantity {}, so that we can use the = Operator to judge and so on.

Whether you like it or not, we will use _ as a "Placeholder" for simplicity ". Now we can define a new curry function, as shown below:

function curry (fn, length, args, holes) { length = length || fn.length; args = args || []; holes = holes || []; return function(){  var _args = args.slice(0),   _holes = holes.slice(0),   argStart = _args.length,   holeStart = _holes.length,   arg, i;  for(i = 0; i < arguments.length; i++) {   arg = arguments[i];   if(arg === _ && holeStart) {    holeStart--;    _holes.push(_holes.shift()); // move hole from beginning to end   } else if (arg === _) {    _holes.push(argStart + i); // the position of the hole.   } else if (holeStart) {    holeStart--;    _args.splice(_holes.shift(), 0, arg); // insert arg at index of hole   } else {    _args.push(arg);   }  }  if(_args.length < length) {   return curry.call(this, fn, length, _args, _holes);  } else {   return fn.apply(this, _args);  } }}

The actual code is quite different. Here we have made some records about what these "holes" (holes) parameters are. In general, the Operation responsibilities are the same.

Show our new helper. The following statements are equivalent:

var f = curry(function(a, b, c) { return [a, b, c]; });var g = curry(function(a, b, c, d, e) { return [a, b, c, d, e]; }); // all of these are equivalentf("a","b","c");f("a")("b")("c");f("a", "b", "c");f("a", _, "c")("b");f( _, "b")("a", "c");//=> ["a", "b", "c"] // all of these are equivalentg(1, 2, 3, 4, 5);g(_, 2, 3, 4, 5)(1);g(1, _, 3)(_, 4)(2)(5);//=> [1, 2, 3, 4, 5]

Crazy ?!

Why do I need to care? How can ke Lihua help me?

You may stop thinking...

It looks cool and... But can this really help me write better code?

There are many reasons for the usefulness of function colitization.

Function kerialization allows and encourages you to separate complex functions into smaller and easier parts for analysis. These small logical units are obviously easier to understand and test, and then your application will become a clean and neat combination consisting of some small units.

To give a simple example, let's use Vanilla. js, Underscore. js, and "function-based mode" (extreme use of function-based features) to compile a CSV parser.

Vanilla. js (Imperative)

//+ String -> [String]var processLine = function (line){ var row, columns, j; columns = line.split(","); row = []; for(j = 0; j < columns.length; j++) {  row.push(columns[j].trim()); }}; //+ String -> [[String]]var parseCSV = function (csv){ var table, lines, i;  lines = csv.split("\n"); table = []; for(i = 0; i < lines.length; i++) {  table.push(processLine(lines[i])); } return table;};Underscore.js//+ String -> [String]var processLine = function (row) { return _.map(row.split(","), function (c) {  return c.trim(); });}; //+ String -> [[String]]var parseCSV = function (csv){ return _.map(csv.split("\n"), processLine);};

Function-based mode

//+ String -> [String]var processLine = compose( map(trim) , split(",") ); //+ String -> [[String]]var parseCSV = compose( map(processLine) , split("\n") );

All of these examples are functionally equivalent. I intentionally try to write these as easily as possible.

It is very difficult to achieve a certain effect, but subjective examples I really think that the last example, functional method, reflects the power behind functional programming.

Notes on curry Performance

Some people who are very concerned about performance can look at this. I mean, pay attention to all these extra things?

In general, there are some overhead when using colialize. Depending on what you are doing, it may or will not affect you in an obvious way. That is to say, I dare say that in most cases, the performance bottleneck of your code comes from other reasons first, rather than this.

For performance, here are some things that must be kept in mind:

  • Accessing arguments objects is generally a little slower than accessing named parameters.
  • Some earlier versions of browsers are quite slow in implementing arguments. length.
  • Use fn. apply (... ) And fn. call (... ) Is usually worse than directly calling fn (... ) A little slower
  • Creating a large number of nested scopes and closure functions will incur costs, whether in memory or speed.
  • In most web applications, the "bottleneck" occurs in DOM control. This is very impossible. You are concerned about performance in all aspects. Obviously, you do not need to consider the above Code.
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.