📜 ⬆️ ⬇️

Writing LINQ in JavaScript from scratch

What for


Once, when developing a graphics component for a web browser, we encountered the problem of processing sequences in JavaScript.

C # developers have been using extension methods for sequence processing for many years. And although this technology is not devoid of pitfalls, it allows in many cases to avoid the tedious writing of routine code for processing arrays, lists, etc.

Naturally, I would like to have similar features in JavaScript.
')
At the moment there are many implementations of LINQ for JavaScript, for example, linq.js , $ linq . These implementations provide ample opportunities comparable to C # implementations. The downside is the considerable size of the code, from which only a small part can be used in a particular application. For example, we need only the distinct function, and we are forced to add kilobytes of the code of the third-party library.

Again, it is curious to understand how a particular library works, and it is often tiresome to parse the source code of a large library. Or, in our case, we already have our own library for JavaScript and we just want to compactly supplement it with the functionality of working with sequences.

How useful is JavaScript to implement?


We define the JavaScript elements that are needed to implement the plan. We have arrays, there are lambda functions, there are objects, inheritance is possible with some reservations. We want to perform the operations represented by lambda functions on the data contained in the arrays and we want to group these operations in an object. Everything is good.

Try it yourself


Let's start by defining the necessary entities.
Obviously, for working with sequences, the most basic object is an iterator. All that is needed from him are three methods:

So, we need a method that constructs an iterator from the base sequence (for example, from the JS array).

Next, we need an object that will contain a set of methods for working with sets (where, select, etc.). Since I wanted to have a name reflecting the essence of the functional, rather than “hinting” on analogs, the word Walkable was selected (the essence of which you can “walk” / iterate on the elements). Each method of this object must also return a Walkable object in order to support the ability to combine several calls in one chain (in other words, so that you can transfer the result of the previous method to the next one).

Secondly, we need a data source. It would not be desirable to be limited to the built-in JS array as the only source. This is where the iterator mentioned above comes in handy. All we need from the sequence is the only method that returns an iterator (of course, every time a new instance is used so that it can be used without changing the state of other iterators). In the next step, Walkable methods can be “mixed in” to such an object, and as a result, any Walkable methods can be invoked over the result and cascaded into arbitrary chains.

Let's illustrate the theory with a code. Here is the method that creates the Walkable object from the JS array:

var arrayWalkable = function(arr) { var walker = function() { .//  -  var idx = -1; //   //     this.reset = function() { idx = -1; }; this.moveNext = function() { return (++idx < arr.length); }; this.current = function() { return (idx < 0 || idx >= arr.length) ? null : arr[idx]; }; }; //    ,   var walkable = { getWalker: function() { return new walker(); },}; //     Walkable // WAVE.extend -    ,    / // WAVE.extend(walkable, WAVE.Walkable); return walkable; } 

Next, let's take the Walkable object itself. It must contain methods for working with sequences, each of which must return a new Walkable object in its turn.

So, we implement the Walkable object and the most popular where method. Since we chose walkable as the name, we call all extension methods with the letter “w”:

 wWhere: function(filter) { var srcWalkable = this; var walkable = { getWalker: function() { var walker = srcWalkable.getWalker(); return { reset: function() { walker.reset(); }, moveNext: function() { while (walker.moveNext()) { var has = filter(walker.current()); if (has) return true; } return false; }, current: function() { return walker.current(); } }; } } WAVE.extend(walkable, WAVE.Walkable); return walkable; }, //wWhere 

As an argument for wWhere, we take the filter function, which in turn takes the sequence element as input and returns true or false (whether or not to include the element in the result). Next we memorize this in the srcWalkable variable (a classic technique in JS).

In the next step, by analogy with the above described arrayWalkable method, we determine the base walkable object with the key getWalker method. With each call, we get a new iterator of the current object, so as not to affect other iterators. And return the object.

Then we redefine the iterator's methods in accordance with the logic of the where operation:

Usage example


Fragments of unit tests can be cited as the simplest examples of use.

Let us demonstrate the work of the iterator itself and walkable:

It is easy to see that such a design allows building arbitrary functions that return Walkable into chains of arbitrary length.

By analogy with wWhere, the remaining functions of our library are implemented, for example, wSelect, wDistinct, etc.

 function() { var a = [1, 2, 3, 4, 5]; var aw = WAVE.arrayWalkable(a); var walker = aw.getWalker(), i=0; while(walker.moveNext()) { assertTrue(a[i] === walker.current()); i++; } assertFalse(walker.moveNext()); assertTrue(null === walker.current()); } 

Now use wWhere, building them into a chain:

 function() { var a = [1, 2, 3, 4, 5]; var aw = WAVE.arrayWalkable(a); var wWhere1 = aw.wWhere(function(e) { return e > 1; }); var wWhere2 = wWhere1.wWhere(function(e) { return e < 5; }); var result2 = wWhere2.wToArray(); for(var i in result2) log(result2[i]); assertTrue( 3 === result2.length); } 

The code uses wrapper functions for testing included in wv.js.

findings


Implementing your own LINQ-like library in JavaScript is not a difficult task and anyone with minimal programming knowledge can handle it.

Nevertheless, in our experience, such a library makes it very cool to write logic based on working with a series of data, while taking up as much space in the code as we need from it.

The source code of the library can be obtained here , unit tests are here .
P.S:
I decided to add a complete list of Walkable functions:
wSelect, wSelectMany, wWhere, wArt, wFe; wToArray, wEach, wWMA (moving average), wHarmonicFrequencyFilter (our own filter, the concept proved to be excellent, for example, here ), wConstLinearSampling, wSineGen, wSawGen, wSquareGen, wRandomGen,

Source: https://habr.com/ru/post/261903/


All Articles