angular-ui-form-validation
Version:
A plugin for performing validation in angularjs without writing lots of boilerplate code or duplicating logic.
259 lines (179 loc) • 11 kB
Markdown
Like Underscore, but lazier
===========================
[](https://travis-ci.org/dtao/lazy.js)
[](http://badge.fury.io/bo/lazy.js)
[](http://badge.fury.io/js/lazy.js)
**Lazy.js** it a utility library for JavaScript, similar to [Underscore](http://underscorejs.org/) and [Lo-Dash](http://lodash.com/) but with one important difference: **lazy evaluation** (also known as deferred execution). This can translate to superior performance in many cases, *especially* when dealing with large arrays and/or "chaining" together multiple methods. For simple cases (`map`, `filter`, etc.) on small arrays, Lazy's performance should be similar to Underscore or Lo-Dash.
The following chart illustrates the performance of Lazy.js versus Underscore and Lo-Dash for several common operations using arrays with 10 elements each on Chrome:

You can see that the performance difference becomes much more significant for methods that don't require iterating an entire collection (e.g., `indexOf`, `take`) as the arrays get larger:

Intrigued? Great! Lazy.js has no external dependencies, so you can get started right away with:
```html
<script type="text/javascript" src="lazy.js"></script>
<!-- optional: if you want support for DOM event and AJAX-based sequences: -->
<script type="text/javascript" src="lazy.browser.js"></script>
```
Or, if you're using Node.js:
```bash
npm install lazy.js
```
Now let's look at what you can do with Lazy.js. (For more thorough information, take a look at the [API Docs](http://dtao.github.io/lazy.js/docs/).)
Introduction
------------
We'll start with an array containing 1000 integers. Incidentally, generating such an array using Lazy.js is quite trivial:
```javascript
var array = Lazy.range(1000).toArray();
```
Note the `toArray` call; without it, what you'll get from `Lazy.range` won't be an actual *array* but rather a `Lazy.Sequence` object, which you can iterate over using `each`. But we'll get to that in a moment.
Now let's say we want to take the *squares* of each of these numbers, increment them, and then take the first five even results. We'll use these helper functions, to keep the code concise:
```javascript
function square(x) { return x * x; }
function inc(x) { return x + 1; }
function isEven(x) { return x % 2 === 0; }
```
Yes, this is admittedly a very arbitrary goal. (Later I'll get around to thinking of a more realistic scenario.) Anyway, here's one way you might accomplish it using Underscore and its convenient `chain` method:
```javascript
var result = _.chain(array).map(square).map(inc).filter(isEven).take(5).value();
```
This query does a lot of stuff:
- `map(square)`: iterates over the array and creates a new 1000-element array
- `map(inc)`: iterates over the new array, creating *another* new 1000-element array
- `filter(isEven)`: iterates over *that* array, creating yet *another* new (500-element) array
- `take(5)`: all that just for 5 elements!
So if performance and/or efficiency were a concern for you, you would probably *not* do things that way using Underscore. Instead, you'd likely go the procedural route:
```javascript
var results = [];
for (var i = 0; i < array.length; ++i) {
var value = (array[i] * array[i]) + 1;
if (value % 2 === 0) {
results.push(value);
if (results.length === 5) {
break;
}
}
}
```
There—now we haven't created any extraneous arrays, and we did all of the work in one iteration. Any problems?
Well, yeah. The main problem is that this is one-off code, which isn't reusable and took a bit of time to write. If only we could somehow leverage the expressive power of Underscore but still get the performance of the hand-written procedural solution...
***
That's where Lazy.js comes in! Here's how we'd write the above query using Lazy.js:
```javascript
var result = Lazy(array).map(square).map(inc).filter(isEven).take(5);
```
Looks almost identical, right? That's the idea: Lazy.js aims to be completely familiar to experienced JavaScript devs. Every method from Underscore should have the same name and identical behavior in Lazy.js, except that instead of returning a fully-populated array on every call, it creates a *sequence* object with an `each` method.
What's important here is that **no iteration takes place until you call `each`**, and **no intermediate arrays are created**. Essentially Lazy.js combines all query operations into a sequence that behaves quite a bit like the procedural code we wrote a moment ago.
Of course, *unlike* the procedural approach, Lazy.js lets you keep your code clean and functional, and focus on buliding an application instead of optimizing array traversals.
Features
--------
OK, cool. What else can Lazy.js do?
### Indefinite sequence generation
The sequence-based paradigm of Lazy.js lets you do some pretty cool things that simply aren't possible with Underscore's array-based approach. One of these is the generation of **indefinite sequences**, which can go on forever, yet still support all of Lazy's built-in mapping and filtering capablities.
Want an example? Sure thing! Let's say we want 300 unique random numbers between 1 and 1000.
```javascript
var uniqueRandsFrom1To1000 = Lazy.generate(function() { return Math.random(); })
.map(function(e) { return Math.floor(e * 1000) + 1; })
.uniq()
.take(300);
// Output: see for yourself!
uniqueRandsFrom1To1000.each(function(e) { console.log(e); });
```
Pretty neat. How about a slightly more advanced example? Let's use Lazy.js to make a [Fibonacci sequence](http://en.wikipedia.org/wiki/Fibonacci_number).
```javascript
var fibonacci = Lazy.generate(function() {
var x = 1,
y = 1;
return function() {
var prev = x;
x = y;
y += prev;
return prev;
};
}());
// Output: undefined
var length = fibonacci.length();
// Output: [2, 2, 3, 4, 6, 9, 14, 22, 35, 56]
var firstTenFibsPlusOne = fibonacci.map(inc).take(10).toArray();
```
OK, what else?
### Asynchronous iteration
You've probably [seen code snippets before](https://gist.github.com/dtao/2351944) that show how to iterate over an array asynchronously in JavaScript. But have you seen an example packed full of map-y, filter-y goodness like this?
```javascript
var asyncSequence = Lazy(array)
.async(100) // specifies a 100-millisecond interval between each element
.map(inc)
.filter(isEven)
.take(20);
// This function returns immediately and begins iterating over the sequence asynchronously.
asyncSequence.each(function(e) {
console.log(new Date().getMilliseconds() + ": " + e);
});
```
All right... what else?
### Event sequences
With indefinite sequences, we saw that unlike Underscore and Lo-Dash, Lazy.js doesn't actually need an in-memory collection to iterate over. And asynchronous sequences demonstrate that it also doesn't need to do all its iteration at once.
Now here's a really cool combination of these two features: with a small extension to Lazy.js (lazy.browser.js, a separate file to include in browser-based environments), you can apply all of the power of Lazy.js to **handling DOM events**. In other words, Lazy.js lets you think of DOM events as a *sequence*—just like any other—and apply the usual `map`, `filter`, etc. functions on that sequence.
Here's an example. Let's say we want to handle all `mousemove` events on a given DOM element, and show their coordinates in one of two other DOM elements depending on location.
```javascript
// First we define our "sequence" of events.
var mouseEvents = Lazy(sourceElement).on("mousemove");
// Map the Event objects to their coordinates, relative to the element.
var coordinates = mouseEvents.map(function(e) {
var elementRect = sourceElement.getBoundingClientRect();
return [
Math.floor(e.clientX - elementRect.left),
Math.floor(e.clientY - elementRect.top)
];
});
// For mouse events on one side of the element, display the coordinates in one place.
coordinates
.filter(function(pos) { return pos[0] < sourceElement.clientWidth / 2; })
.each(function(pos) { displayCoordinates(leftElement, pos); });
// For those on the other side, display them in a different place.
coordinates
.filter(function(pos) { return pos[0] > sourceElement.clientWidth / 2; })
.each(function(pos) { displayCoordinates(rightElement, pos); });
```
Anything else? Of course!
### String processing
Now here's something you may not have even thought of: `String.match` and `String.split`. In JavaScript, each of these methods returns an *array* of substrings. If you think about it, this often means doing more work than necessary; but it's the quickest way (from a developer's standpoint) to get the job done.
For example, suppose you wanted the first five lines of a block of text. You could always do this:
```javascript
var firstFiveLines = text.split("\n").slice(0, 5);
```
But of course, this actually splits *the entire string* into every single line. If the string is very large, this is quite wasteful.
With Lazy.js, we don't need to split up an entire string just to treat it as a sequence of lines. We can get the same effect by wrapping the string with `Lazy` and calling `split`:
```javascript
var firstFiveLines = Lazy(text).split("\n").take(5);
```
This way we can read the first five lines of an arbitrarily large string (without pre-populating a huge array) and map/reduce on it just as with any other sequence.
Similarly with `String.match`: let's say we wanted to find the first 5 alphanumeric matches in a string. With Lazy.js, it's easy!
```javascript
var firstFiveWords = Lazy(text).match(/[a-z0-9]+/i).take(5);
```
Piece of cake.
### Stream processing
Lazy.js can wrap *streams* in Node.js as well.
Given any [`Readable Stream`](http://nodejs.org/api/stream.html#stream_class_stream_readable), you can wrap it with `Lazy` just as with arrays:
```javascript
Lazy(stream)
.take(5) // Read just the first 5 chunks of data read into the buffer.
.each(processData);
```
For convenience, specialized helper methods for dealing with either file streams or HTTP streams are also offered. (**Note: this API will probably change.**)
```javascript
// Read the first 5 lines from a file:
Lazy.readFile("path/to/file")
.lines()
.take(5)
.each(doSomething);
// Read lines 5-10 from an HTTP response.
Lazy.makeHttpRequest("http://example.com")
.lines()
.drop(5)
.take(5)
.each(doSomething);
```
In each case, the elements in the sequence will be "chunks" of data most likely comprising multiple lines. The `lines()` method splits each chunk into lines (lazily, of course).
***
**This library is experimental and still a work in progress.**