Last active
April 25, 2017 22:14
-
-
Save jkachmar/6e55f39af11a68e20e3c2db7138abe7c to your computer and use it in GitHub Desktop.
partial.lenses tutorial
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const R = require('ramda'); | |
| const L = require('partial.lenses'); | |
| /* -------------------------------------------------------------------------- */ | |
| const arr = [1, 2, 3]; | |
| // 'get', pulls value from some data structure | |
| L.get(L.index(0), arr); | |
| // > 1 | |
| // 'set', returns a copy of some data structure with updated fields | |
| L.set(L.index(0), 0, arr); | |
| // > [0, 2, 3] | |
| /* -------------------------------------------------------------------------- */ | |
| const record = { | |
| field1: 0, | |
| field2: 0 | |
| }; | |
| // NOTE: `'field1'` is shorthand for `L.prop('field1')` | |
| L.get('field1', record); | |
| // > 0 | |
| /* -------------------------------------------------------------------------- */ | |
| const nested = { | |
| foo: { | |
| bar: { | |
| baz: 0 | |
| } | |
| } | |
| }; | |
| // NOTE: [x, y, z] is shorthand for L.compose(x, y, z) | |
| // Focusing 'optic' for `baz` within the `nested` object | |
| const fooBarBaz = ['foo', 'bar', 'baz']; | |
| L.get(fooBarBaz, nested); // > 0 | |
| // NOTE: partial.lenses automatically calls Object.freeze when operating in | |
| // a non-production environment | |
| L.modify(fooBarBaz, x => x + 1, nested); | |
| // > { foo: { bar: { baz: 1 } } } | |
| /* -------------------------------------------------------------------------- */ | |
| // Following along with the partial.lenses tutorial | |
| const sampleTitles = { | |
| titles: [ { language: 'en', text: 'Title' }, | |
| { language: 'sv', text: 'Rubrik' }] | |
| }; | |
| /* -------------------------------------------------------------------------- */ | |
| // Small lenses can be combined through basic function composition | |
| // This is a lens that focuses on the text value for a given language key | |
| const textIn = language => | |
| ['titles', | |
| L.define([]), | |
| L.normalize(R.sortBy(L.get('language'))), | |
| L.find(R.whereEq({language})), | |
| L.valueOr({language, text: ''}), | |
| L.removable('text'), | |
| 'text']; | |
| // A getter for the text value for the 'sv' language code | |
| L.get(textIn('sv'), sampleTitles); | |
| // > 'Rubrik' | |
| // A getter for the text value for the 'en' language code | |
| L.get(textIn('en'), sampleTitles); | |
| // > 'Title' | |
| // A getter for the text value for a non-existent language code, which | |
| // falls back to the default given by `L.valueOr({language, text: ''})` | |
| L.get(textIn('fi'), sampleTitles); | |
| // > '' | |
| // A setter for a non-existent field, which creates the surrounding structure | |
| // in the | |
| L.set(textIn('fi'), 'Otsikko', sampleTitles); | |
| // > { titles: | |
| // > [ { language: 'en', text: 'Title' }, | |
| // > { language: 'fi', text: 'Otsikko' }, | |
| // > { language: 'sv', text: 'Rubrik' } ] } | |
| // Sets a field as undefined, removing it from the focused structure | |
| L.remove(textIn('sv'), sampleTitles); | |
| // > { titles: [ { language: 'en', text: 'Title' } ] } | |
| // Removes all the fields, which _doesn't_ result in an empty/undefined object | |
| // as `L.define([])` designates `[]` as the 'empty' structure for this lens | |
| R.compose(L.remove(textIn('sv')), | |
| L.remove(textIn('en')) | |
| )(sampleTitles); | |
| // > { titles: [] } | |
| /* -------------------------------------------------------------------------- */ | |
| // Lenses can be structured similarly to the object we're accessing | |
| // A 'title' is a removable field with the key 'text'. | |
| const Title = { | |
| text: [L.removable('text'), 'text'] | |
| }; | |
| // A member of the array of 'titles' can be accessed given the value of its | |
| // language key, if no 'title' exists that matches the given key, return an | |
| // empty string. | |
| const Titles = { | |
| titleIn: language => | |
| [L.find(R.whereEq({language})), | |
| L.valueOr({language, text: ''})] | |
| }; | |
| const Model = { | |
| titles: ['titles', | |
| L.define([]), | |
| L.normalize(R.sortBy(L.get('language')))], | |
| textIn: language => [Model.titles, | |
| Titles.titleIn(language), | |
| Title.text] | |
| }; | |
| L.get(Model.textIn('sv'), sampleTitles); | |
| // > Rubrik | |
| /* -------------------------------------------------------------------------- */ | |
| // Traversals are optics that focus on multiple targets in a structure | |
| // The result of composing a lens with a traversal is another traversal | |
| const texts = [Model.titles, | |
| L.elems, | |
| Title.text]; | |
| // This traversal focuses on the all the titles in our 'sampleTitles' object | |
| L.collect(texts, sampleTitles); | |
| // > [ 'Title', 'Rubrik' ] | |
| // Traversals can be mapped and reduced over | |
| const Max = {empty: () => 0, concat: Math.max}; | |
| // This traversal finds the maximum length of a 'title' element in 'sampleTitles' | |
| L.concatAs(R.length, Max, texts, sampleTitles); | |
| // > 6 | |
| // Traversals can also modify the contents they focus on... | |
| L.modify(texts, R.toUpper, sampleTitles); | |
| // > { titles: | |
| // > [ { language: 'en', text: 'TITLE' }, | |
| // > { language: 'sv', text: 'RUBRIK' } ] } | |
| // ...as well as selectively modify those contents | |
| L.remove([texts, | |
| L.when(t => t.length > 5)], | |
| sampleTitles); | |
| // > { titles: [ { language: 'en', text: 'Title' } ] } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment