UNPKG

nanoscope

Version:

A Lens Library for Javascript

455 lines (348 loc) 20.2 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>nanoscope Index</title> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/sunlight.default.css"> <link type="text/css" rel="stylesheet" href="styles/site.spacelab.css"> </head> <body> <div class="container-fluid"> <div class="navbar navbar-fixed-top "> <div class="navbar-inner"> <a class="brand" href="index.html">nanoscope</a> <ul class="nav"> <li class="dropdown"> <a href="classes.list.html" class="dropdown-toggle" data-toggle="dropdown">Classes<b class="caret"></b></a> <ul class="dropdown-menu "> <li> <a href="Compose.html">Compose</a> </li> <li> <a href="Getter.html">Getter</a> </li> <li> <a href="IndexedLens.html">IndexedLens</a> </li> <li> <a href="IndexedLens.Unsafe.html">IndexedLens.Unsafe</a> </li> <li> <a href="Lens.html">Lens</a> </li> <li> <a href="MultiLens.html">MultiLens</a> </li> <li> <a href="Optional.html">Optional</a> </li> <li> <a href="PathLens.html">PathLens</a> </li> <li> <a href="PathLens.Unsafe.html">PathLens.Unsafe</a> </li> <li> <a href="Setter.html">Setter</a> </li> <li> <a href="SliceLens.html">SliceLens</a> </li> </ul> </li> <li class="dropdown"> <a href="global.html" class="dropdown-toggle" data-toggle="dropdown">Global<b class="caret"></b></a> <ul class="dropdown-menu "> <li> <a href="global.html#get">get</a> </li> <li> <a href="global.html#IdLens">IdLens</a> </li> <li> <a href="global.html#map">map</a> </li> </ul> </li> </ul> </div> </div> <div class="row-fluid"> <div class="span8"> <div id="main"> <span class="page-title">Index</span> <section class="readme-section"> <article><p><a href="https://github.com/5outh/nanoscope"><img src="NanoscopeLogo.png" width="400px"></img></a></p> <h2>A Lens Library for Javascript</h2><p>Installation is easy:</p> <pre class="prettyprint source"><code>$ npm install nanoscope</code></pre><h3>What is a Lens?</h3><p>A <code>Lens</code> is a construct that allow you to peer into data structures and modify their contents. At base level, a <code>Lens</code> consists of a getter and a mapping function over a specific sub-part of your data. <code>Lens</code>es allow you to modify data in interesting ways with minimal code, and <code>nanoscope</code> contains many useful <code>Lens</code>es that you can plug into your existing code and use right out of the box, that provide things like:</p> <ul> <li>Safe traversal of deeply nested data structures</li> <li>Easy access and modification of single array elements</li> <li>Access and modification of complete array slices, including modifications of size</li> <li>A wrapper for safe access and modification of data through any <code>Lens</code></li> </ul> <h3>The <code>Lens</code> Interface</h3><p><code>Lens</code>es support the following operations:</p> <ul> <li><code>get</code>, which gets the value at the focus of the <code>Lens</code></li> <li><code>set</code>, which sets the value at the focus of the <code>Lens</code></li> <li><code>map</code>, which maps a function over the focus of the <code>Lens</code></li> <li><code>view</code>, which sets the view of the <code>Lens</code> to a new value</li> <li><code>compose</code>, which composes the <code>Lens</code> with another lens, allowing sequencing of actions.</li> <li><code>add</code>, which adds another <code>Lens</code> focus to the lens, allowing multiple focal points.</li> </ul> <p>Assuming <code>headLens</code> is a <code>Lens</code> that focuses on the first element of an array, they can be used like this:</p> <pre class="prettyprint source lang-js"><code>headLens.get([1, 2, 3]); // => 1 // or headLens.view([1, 2, 3]).get(); // => 1 headLens.set([1, 2, 3], 99); // => [99, 2, 3] // or headLens.view([1, 2, 3]).set(99); // => [99, 2, 3] headLens.map([1, 2, 3], function (elem) { return elem * 10; }); // => [10, 2, 3] // or headLens.view([1, 2, 3]).map(function (elem) { return elem * 10; }); // => [10, 2, 3] headLens.compose(headLens).view([['what'], 2, 3]).get(); // => 'what' // Assume lastLens focuses on the last element headLens.compose(lastLens).view([1, 2, 3]).get(); // => [1, 3]</code></pre><p>Of particular interest is <code>compose</code>, which allows us to compose a <code>headLens</code> with a <code>headLens</code> to focus on an array's first element <em>of it's first element</em>, and <code>add</code>, which allows us to focus on both the first and last elements of the array in parallel.</p> <h3>IndexedLens</h3><p><code>IndexedLenses</code> focus on a single element of an array, specified by its index. <code>headLens</code> as shown above can be built using an <code>IndexedLens</code> like so:</p> <pre class="prettyprint source lang-js"><code>var headLens = new nanoscope.IndexedLens(0);</code></pre><p>This means that we are focusing on the <code>0</code>-th element of an array. <code>IndexedLenses</code> are <em>safe</em> by default, which means that they will not throw errors when you try to access elements out of range. For example, <code>headLens.view([]).get()</code> will not throw an error. To make an <em>unsafe</em> <code>IndexedLens</code>, just use the <code>Unsafe</code> constructor:</p> <pre class="prettyprint source lang-js"><code>var unsafeHeadLens = new nanoscope.IndexedLens.Unsafe(0);</code></pre><p>In an unsafe <code>IndexedLens</code>, the following operations will throw an error:</p> <ul> <li><code>get()</code>, if the index is greater than or equal to the length of the array, and</li> <li><code>set()</code>, if the index is strictly greater than the length of the array (you may tack on items to the end of an array)</li> </ul> <h3>SliceLens</h3><p><code>SliceLenses</code> focus on a subarray within an array. They can be constructed in two ways:</p> <ol> <li>By specifying <code>start</code> (inclusive) and <code>end</code> (exclusive) indices in the constructor, like so:</li> </ol> <pre class="prettyprint source lang-js"><code>var firstTwo = new nanoscope.SliceLens(0, 2);</code></pre><ol> <li>By specifying a python-style slice as a string as a single argument:</li> </ol> <pre class="prettyprint source lang-js"><code>var firstTwo = new nanoscope.SliceLens('0:2');</code></pre><p>By using the second syntax, you can use any of the python type variants using <code>:</code>. For example:</p> <pre class="prettyprint source lang-js"><code>// a `Lens` that focuses on everything but the first element var tailLens = new nanoscope.SliceLens('1:'); // a `Lens` that focuses on everything but the last element var initLens = new nanoscope.SliceLens(':-1');</code></pre><p>Negative indices are accepted, which count backwards from the end of the list.</p> <p><code>SliceLenses</code> can be used not only to modify the elements in each slice, but it can also modify the length of the slice. For example:</p> <pre class="prettyprint source lang-js"><code>initLens.view([1, 2, 3, 4]).map( function (arr) { return _.map( arr, function (elem) { return elem * 2; } ) }); // => [1, 4, 6, 4] // Assume `sum` sums the elements in a list initLens.view([1, 2, 3, 4]).map(sum); // => [6, 4]</code></pre><h3>PathLens</h3><p><code>PathLenses</code> are used to access nested data inside dynamic objects. They are constructed by passing a string representation of the path followed to get to the element to focus on, separated by <code>.</code>. They are safe by default; this is best illustrated by an example.</p> <pre class="prettyprint source lang-js"><code>var testObject = { a : { b: { c : 100 } } }; new nanoscope.PathLens('a.b.c').view(testObject).get(); // => 100 new nanoscope.PathLens('a.b.c.d.e.f').view(testObject).get(); // => null new nanoscope.PathLens('a.b.c').view(testObject).set('foo'); // => testObject.a.b.c == 'foo' new nanoscope.PathLens('a.b.c.d.e.f').view(testObject).set('foo'); // => testObject.a.b.c.d.e.f == 'foo'</code></pre><p>Note that in the last call we're overwriting <code>testObject.a.b.c</code>; this is by design, but something to be aware of. If you prefer that your <code>PathLens</code> throw errors when keys in the path don't exist, you can use the <code>PathLens.Unsafe</code> constructor instead; these are constructed in the same way:</p> <pre class="prettyprint source lang-js"><code>new nanoscope.PathLens.Unsafe('a.b.c.d.e.f').view(testObject).get(); // => TypeError: Cannot read property 'e' of undefined</code></pre><p>One other thing to note is that when using <code>over</code> in any <code>PathLens</code>, if:</p> <ol> <li>You are accessing a field that didn't originally exist, <em>and</em></li> <li>Your function returns <code>undefined</code> or <code>null</code>,</li> </ol> <p>your structure will be unmodified. This prevents things like:</p> <pre class="prettyprint source lang-js"><code>new nanoscope.PathLens('a.b.c.e.f.g').view({}).over(function (elem) { return elem * 2; });</code></pre><p>... from producing this:</p> <pre class="prettyprint source lang-js"><code>{ a: { b: { c: { d: { e: { f: { g: undefined } } } } } } }</code></pre><p>Instead, it will not modify the object and, in this case, simply return <code>{}</code>.</p> <h3>Optional</h3><p><code>Optional</code> <code>Lenses</code> wrap any <code>Lens</code> in a function that catches any errors that may happen along the way. They are constructed with the <code>Optional</code> constructor, which takes any <code>Lens</code> as an argument, along with an optional <code>errorHandler</code> function, This function will be called on any errors that may occur during the execution of any <code>Lens</code> operations, and if omitted, these errors will cause <code>get</code> to silently return <code>null</code>, and <code>set</code>/<code>map</code> to silently return the object passed in. <code>errorHandler</code> may also be a default value that you would prefer to return upon any errors.</p> <p>For example, we can take an <code>Unsafe IndexedLens</code> and wrap it in <code>Optional</code> in order to handle incoming errors as they are thrown:</p> <pre class="prettyprint source lang-js"><code>var Optional = nanoscope.Optional, IndexedLens = nanoscope.IndexedLens, lens; lens = new Optional( new IndexedLens.Unsafe(10) ); lens.view([]).get(); // => null lens.view([]).set(0); // => [] lens = new Optional( new IndexedLens.Unsafe(10), 'FAIL!' ); lens.view([]).get(); // => 'FAIL!' lens.view([]).set(0); // => 'FAIL!' lens = new Optional( new IndexedLens.Unsafe(10), console.log); lens.view([]).get(); // => logs 'Error: Array index 10 out of range', returns undefined lens.view([]).set(0); // => logs 'Error: Array index 10 out of range', returns undefined</code></pre><p>One major thing to note is that <code>Optional Lenses</code> do <strong>not</strong> catch errors from calls to unimplemented functions in <code>Getters</code> and <code>Setters</code>. That is, calling <code>setter.get</code> and <code>getter.set</code> will still fail. This is by design, as these types of errors are logical in nature and should be caught by the programmer in all cases.</p> <h3>Compose</h3><p><code>Compose</code> is a wrapper (like <code>Optional</code>) that takes two <code>Lenses</code> and returns a new <code>Lens</code> that first focuses on the focus of the first <code>Lens</code>, and then on the second, in sequence. The <code>compose()</code> method constructs a <code>Compose</code> <code>Lens</code> under the hood, so the behavior is exactly the same. For a short example, consider an object with an array for one of the keys:</p> <pre class="prettyprint source lang-js"><code>var obj = { a: { anArray: [99, 2, 3, 4] } }</code></pre><p>And say that we want a <code>Lens</code> that focuses on the second object in <code>anArray</code>. We can easily accomplish this with composite lenses:</p> <pre class="prettyprint source lang-js"><code>var lensA = new nanoscope.PathLens('a.anArray'), lensB = new nanoscope.IndexedLens(1), composite = new nanoscope.Compose(lensA, lensB) // or composite = lensA.compose(lensB); composite.view(obj).get(); // => 2 composite.view(obj).set(1); // => { a: { anArray: [99, 1, 3, 4] } }</code></pre><p><code>composite</code> first looks at the focus of <code>lensA</code>, then at the focus of <code>lensB</code> starting at the focus of <code>lensA</code> and uses this as its own focus.</p> <h3>MultiLens</h3><p><code>MultiLenses</code> allow you to focus on many different things at once and return them all at once. <code>MultiLens</code> is a sort of concurrent version of <code>Compose</code>. It takes either an Array of <code>Lenses</code> or an object with <code>Lenses</code> as values, and produces a <code>Lens</code> whose focus is all of the focuses in this argument. If the argument is an object, <code>get</code> will name each of the outputs in an object; if not, it will return an array of unnamed results. <code>set</code> and <code>map</code> will set <em>every</em> focus of the lens.</p> <p><code>MultiLenses</code> can also be constructed using the <code>add</code> method in any <code>Lens</code>, just like <code>compose</code> above.</p> <p>Here is a simple example of a <code>MultiLens</code> in action:</p> <pre class="prettyprint source lang-js"><code>var arrayLenses = [ new nanoscope.IndexedLens(0), new nanoscope.IndexedLens(1) ], objectLenses = { head: new nanoscope.IndexedLens(0), last: new nanoscope.IndexedLens(-1) }, // A MultiLens built from an Array arrayMultiLens = new nanoscope.MultiLens(arrayLenses), // or arrayMultiLens = new nanoscope.IndexedLens(0).add(new nanoscope.IndexedLens(1)); // A MultiLens built from an Object objectMultiLens = new nanoscope.MultiLens(objectLenses); arrayMultiLens.view([1, 2]).get(); // => [1, 2] arrayMultiLens.view([1, 2]).set('g'); // => ['g', 'g'] objectMultiLens.view([1, 2, 3]).get(); // => { head: 1, last: 3 } objectMultiLens.view([1, 2, 3]).set('g'); // => ['g', 2, 'g']</code></pre><h3>Getters and Setters</h3><p><code>Getters</code> are <code>Lenses</code> that only support <code>get()</code>, and <code>Setters</code> are <code>Lenses</code> that only support <code>over</code> and <code>set</code>. <code>Getters</code> and <code>Setters</code> are constructed with <code>get</code> functions and <code>over</code> functions <em>only</em>, respectively. They can also be constructed by using the <code>fromLens</code> static function in <code>Getter</code> and <code>Setter</code>, which simply replaces the old <code>over</code>/<code>get</code> operations in the original <code>Lens</code>. Constructing your own <code>Lenses</code>, <code>Getters</code> and <code>Setters</code> is described below, but here is an example of how <code>fromLens</code> works:</p> <pre class="prettyprint source lang-js"><code>var Getter = nanoscope.Getter, Setter = nanoscope.Setter, IndexedLens = nanoscope.IndexedLens; Getter.fromLens(new IndexedLens(0)).view([1]).get(); // => 1 Getter.fromLens(new IndexedLens(0)).view([1]).set(10); // => Error: map not permitted in a Getter Setter.fromLens(new IndexedLens(0)).view([1]).set(10); // => [10] Setter.fromLens(new IndexedLens(0)).view([1]).get(); // => Error: get not permitted in a Setter</code></pre><p>These are useful when you want to restrict access to certain parts of your structures, but still use any <code>Lenses</code> to access data in one or more ways.</p> <h3>Making your own <code>Lens</code>es</h3><p>Consider a <code>Lens</code> that views an array and focuses on its first element. The <code>get</code> function for this <code>Lens</code> might look like this:</p> <pre class="prettyprint source lang-js"><code>var get = function (arr) { return arr[0]; };</code></pre><p>...and <code>map</code> might be defined like so:</p> <pre class="prettyprint source lang-js"><code>var map = function (arr, func) { var newArr = _.cloneDeep(arr); newArr[0] = func(newArr[0]); return newArr; };</code></pre><p>There are a couple of things to note here:</p> <ol> <li>We use <code>_.cloneDeep</code> from <code>lodash</code> (or <code>underscore</code>) to clone the object, because <code>Lens</code>es should provide immutable access to data.</li> <li><code>func</code> is a function that operates on the focus of the <code>Lens</code>, which in this case is <code>arr[0]</code>.</li> <li>We return the full, modified structure at the end.</li> </ol> <p>We can construct a <code>Lens</code> from these bindings like so:</p> <pre class="prettyprint source lang-js"><code>var nanoscope = require('nanoscope'), headLens = new nanoscope.Lens(get, map);</code></pre><p>All valid <code>Lens</code>es must also satisfy the so-called &quot;Lens Laws&quot;:</p> <ol> <li>set-get (you get what you put in): <code>lens.get(lens.set(a, b)) = b</code></li> <li>get-set (putting what is there doesn't change anything): <code>lens.set(a, lens.get(a)) = a</code></li> <li>set-set (setting twice is the same as setting once): <code>lens.set(c, lens.set(b, a)) = lens.set(c, a)</code></li> </ol> <p>These laws ensure that <code>map</code>, <code>set</code> and <code>get</code> behave in the manner you'd expect. If you can convince yourself that these laws are satisfied, you can rest easy knowing your <code>Lens</code> is well-behaved.</p></article> </section> </div> <div class="clearfix"></div> <footer> <span class="jsdoc-message"> Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.3.0-alpha11</a> on 2014-12-17T09:37:57-05:00 using the <a href="https://github.com/terryweiss/docstrap">DocStrap template</a>. </span> </footer> </div> <div class="span3"> <div id="toc"></div> </div> <br clear="both"> </div> </div> <!--<script src="scripts/sunlight.js"></script>--> <script src="scripts/docstrap.lib.js"></script> <script src="scripts/bootstrap-dropdown.js"></script> <script src="scripts/toc.js"></script> <script> $( function () { $( "[id*='$']" ).each( function () { var $this = $( this ); $this.attr( "id", $this.attr( "id" ).replace( "$", "__" ) ); } ); $( "#toc" ).toc( { anchorName : function ( i, heading, prefix ) { return $( heading ).attr( "id" ) || ( prefix + i ); }, selectors : "h1,h2,h3,h4", showAndHide : false, scrollTo : "100px" } ); $( "#toc>ul" ).addClass( "nav nav-pills nav-stacked" ); $( "#main span[id^='toc']" ).addClass( "toc-shim" ); $( '.dropdown-toggle' ).dropdown(); // $( ".tutorial-section pre, .readme-section pre" ).addClass( "sunlight-highlight-javascript" ).addClass( "linenums" ); $( ".tutorial-section pre, .readme-section pre" ).each( function () { var $this = $( this ); var example = $this.find( "code" ); exampleText = example.html(); var lang = /{@lang (.*?)}/.exec( exampleText ); if ( lang && lang[1] ) { exampleText = exampleText.replace( lang[0], "" ); example.html( exampleText ); lang = lang[1]; } else { lang = "javascript"; } if ( lang ) { $this .addClass( "sunlight-highlight-" + lang ) .addClass( "linenums" ) .html( example.html() ); } } ); Sunlight.highlightAll( { lineNumbers : false, showMenu : true, enableDoclinks : true } ); } ); </script> <!--Navigation and Symbol Display--> <!--Google Analytics--> </body> </html>