Deep parsing of functions in JavaScript currying Corrie _javascript techniques

Source: Internet
Author: User
Tags csv parser sendmsg javascript currying ocaml

Introduction
let's look at a little problem:
Someone has a problem in the group:
var s = SUM (1) (2) (3) ..... The last alert (s) comes out 6
var s = SUM (1) (2) (3) (4) ..... The last alert (s) comes out 10
Ask how does sum come true?
Just to see the topic, my first reaction is that sum returns a function, but not the final implementation, the impression that I saw a similar principle, but the memory is not clear.

Later, colleagues said, this is called Gerty,
The implementation method is more ingenious:

function sum (x) { 
 var y = function (x) {return 
  sum (x+y) 
 } 
 y.tostring = y.valueof = function () { 
  return x; 
 } 
 return y; 
} 

Now let's take a closer look at currying gerty ~

What is Gerty?

This is a conversion process that transforms a function that accepts multiple parameters into a function that accepts a single argument (note: The first parameter of the original function), and returns a new function that accepts the remaining arguments and returns the result if the other arguments are necessary.

When we say this, I think Corrie sounds pretty simple. How do you do that in JavaScript?
Let's say we're going to write a function that accepts 3 parameters.

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

Now, suppose we have a gerty function that converts a traditional JavaScript function into a Cory-like 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 Corrie

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

Uncurried
var example1 = function (A, B, c) {
 
//do something with a, B, and C
};
 
Curried
var example2 = function (a) {return
 function (b) {return
  function (c) {
   
//do something WI Th A, B, and C};;};

In JavaScript, the function will still be invoked even if you do not specify all the parameters of a function. This is a very practical JavaScript function, but it creates trouble for Gerty.

The idea is that every function is a function that has and has only one parameter. If you want to have multiple parameters, you must define a series of nested functions. Hate! Doing this twice is OK, but when you need to define a function that requires a lot of parameters in this way, it becomes rather verbose and difficult to read. (But don't worry, I'll tell you a way right away)

Some functional programming languages, like Haskell and OCaml, have built-in functions in the syntax of Gerty. In these languages, for example, each function is a function that has a parameter, and there is only one argument. You might think that limiting trouble is better than good, but that's the grammar of language, which is almost imperceptible.

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

Let example1 = fun a b c->
 
//(* Doing something with a, B, c *) let
 
example2 = Fun a->
 fun b->
  F UN c->
   
//(* Do something with A, B, c *)

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

The difference, however, is that the same thing is done in OCaml. OCaml, there is no function with more than one parameter. However, declaring multiple arguments on one line is a nested definition of a "shortcut" to a single parameter function.

Similarly, we look forward to calling the OCaml function in syntax and in the invocation of the multiple parameter functions similar. We expect this to invoke the above function:

example1 foo bar baz
example2 foo bar baz

And in JavaScript, we take a markedly different approach:

Example1 (foo, bar, Baz);
Example2 (foo) (bar) (Baz);

In languages such as OCaml, Cory is built in. In JavaScript, Corrie is possible (higher-order functions), but is syntactically inconvenient. That's why we decided to write a gerty function to help us do these tedious things and make our code simple.

Create a curry helper function

Theoretically we expect that there can be a convenient way to convert ordinary old-fashioned JavaScript functions (multiple parameters) to fully-gerty functions.

The idea is not unique to me, and others have been implemented, such as the. Autocurry () function in the Wu.js library (although you are concerned with our own implementation).

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 function of this function. Quite simple. Sub_curry accepts a function of FN as its first argument followed by any number of input parameters. Returns a function that returns the result of the fn.apply execution, which merges the first incoming arguments of the function, plus the incoming arguments when the FN call.

See Example:

var fn = function (A, B, c) {return [A, b, c];};
 
These are all equivalent
fn ("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 to be a bit of a gerty's meaning. Now we're going to define the curry of the Gerty 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)
    : S Ub_curry.call (this, combined);
  else {
   
//All arguments have been specified, actually call function return
   fn.apply (this, arguments);
 };
}

This function accepts two parameters, a function, and the number of arguments to "curry". The second argument is optional, and if omitted, the default use of the Function.prototype.length property is to tell you that the function defines several parameters.

Finally, we can demonstrate the following behavior:

var fn = Curry (function (A, B, c) {return [A, b, c];});
 
These are all equivalent
fn ("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 a minute... What the?!

Are you out of your mind? That should be it! We can now write the Gerty functions in JavaScript, acting like those in OCaml or Haskell. Even if I wanted to pass more than one argument at a time, I could just separate the arguments with commas as I did before. There is no need for the ugly parentheses between the parameters, even if it is gerty.

This is pretty useful, and I'll talk about this right away, but first I'm going to make this curry function a small step forward.

Gerty and the "hole" ("holes")

Although the Gerty function is already very bull, it also allows you to spend a little bit of time on the order of the parameters of the function you define. After all, the idea behind Gerty is to create functions, more specific functions, separate 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, in some functional programming languages, a special "placeholder variable" is defined. An underscore is usually specified to do this, as an argument to a function is passed in, indicating that it can be "skipped". is yet to be specified.

This is very useful when you want to apply (partially apply) a specific function in a step-by-step way, but the parameters you want to distribute apply (partially apply) are not the leftmost parameters.

For instance, we have a function like this:

var sendajax = function (URL, data, options) {/ 
* ... */
 }

Perhaps we would like to define a new function, in part we provide the Sendajax function-specific options, but allow URLs and data to be specified.

Of course, we can fairly easily define functions like this:

var sendpost = function (URL, data) {return
 sendajax (URL, data, {type: "POST", ContentType: "Application/json"}); 
   };

Alternatively, use an underscore that uses a convention, as in the following way:

var sendpost = Sendajax (_, _, {type: "POST", ContentType: "Application/json"});

Note that the two parameters are underlined by the way they are passed in. Obviously, JavaScript does not have such native support, so how can we do that?

Turn around and let's make the curry function a little more intelligent ...

First we define our "placeholder" as a global variable.

var _ = {};

We define it as an object literal {}, so that we can use the = = operator to make the sentence.

Whether you like it or not, for the sake of simplicity, we use _ to make "placeholder". Now we can define a new curry function, just like the following:

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.APPL Y (this, _args);
  }
 }

The actual code still has a huge difference. We've made some records of what these "hole" parameters are holes. Generally, the duties of running are the same.

Show us the 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 equivalent
f ("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 equivalent
g (1, 2, 3, 4, 5);
G (_, 2, 3, 4, 5) (1);
G (1, _, 3) (_, 4) (2) (5);
=> [1, 2, 3, 4, 5]

Crazy, huh?!

Why should I care? How can Gerty help me?

You might stop here and think ...

This looks pretty cool and ... But is this really going to help me write better code?

There are a number of reasons why the function of Gerty is useful.

The function of Gerty allows and encourages you to separate complex functions into smaller, more easily analyzed parts. These small logical units are clearly easier to understand and test, and then your application becomes a clean and tidy combination of small units.

To give a simple example, let's use Vanilla.js, underscore.js, and "functional" (extreme use of functional attributes) to write 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);


Functional approach

+ 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 intend to write these as simply as possible.

It's hard to achieve something, but subjectively, I really think the last example, the functional approach, reflects the power behind functional programming.

Notes on performance of curry

Some people who are extremely concerned about performance can look here, I mean, focus on all this extra stuff?

Usually, there is some overhead in using Gerty. Depending on what you're doing, it might or may not, affect you in obvious ways. In other words, I dare say that in almost most cases, your code's own performance bottleneck comes first from other sources, not this one.

For performance, here are some things to keep in mind:

    • Accessing arguments objects is usually slower than accessing named parameters
    • Some older browsers are pretty slow to implement on the arguments.length.
    • Use Fn.apply (...) and Fn.call (...) Usually a little slower than directly invoking FN (...)
    • Creating a large number of nested scopes and closure functions can bring costs, whether in memory or speed
    • In most Web applications, "bottlenecks" occur on the control DOM. It is very unlikely that you are concerned about performance in all aspects. Obviously, do not use the above code to consider themselves.

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.