Last active
June 5, 2025 19:20
-
-
Save gatlin/264d49cf322aef31beccf680093bef38 to your computer and use it in GitHub Desktop.
Revisions
-
gatlin revised this gist
Jun 30, 2016 . 1 changed file with 0 additions and 169 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,59 +1,4 @@ 'use strict'; if(!Function.prototype.compose) { Function.prototype.compose = function(g) { @@ -68,26 +13,6 @@ let replace = function(key, value, thing) { thing[key] = value; return thing; }; let Setter = (x) => Object.seal({ value: x, map: (f) => Setter(f(x)) @@ -100,105 +25,11 @@ let Getter = (x) => Object.seal({ let extract = (s) => s.value; let lens = (key) => (fn) => (s) => fn(s[key]).map((v) => replace(key, v, s)); let get = (l) => (s) => extract.compose(l(Getter))(s); let over = (l) => (f) => (s) => extract.compose(l(Setter.compose(f)))(s); let constant = (x) => (y) => x; let set = (l) => (v) => (s) => over(l)(constant(v))(s); -
gatlin revised this gist
Jun 30, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,5 @@ What is a lens? === *Note: you can copy the file `2-lenses.js` below over to [repl.it][1] and play along from home!* -
gatlin revised this gist
Jun 30, 2016 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,8 +1,8 @@ What is a lens? --- *Note: you can copy the file `2-lenses.js` below over to [repl.it][1] and play along from home!* [1]: https://repl.it/languages/javascript -
gatlin revised this gist
Jun 30, 2016 . 2 changed files with 212 additions and 0 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,212 @@ What is a lens? --- *Note: you can copy the file `2-lenses.js` below this over to [repl.it][1] and play along from home!* [1]: https://repl.it/languages/javascript A lens in programming, much like a lens in the real world, allows you to focus in on a small part of a larger whole and then do something with or to that part. You may also think of it as breaking a piece out, manipulating the piece, and then reconstructing the whole. That "something" can be changing the value or merely passing the value elsewhere to be read. Thus, lenses are both getters *and* setters. Here's an example of the code we'll be implementing: ```javascript let foo = { bar: 5, baz: [ 'quux', 'axolotl' ] }; let barL = lens('bar'); let bazL = lens('baz'); let bazAt = (index) => bazL.compose(lens(index)); let axolotl = get(bazAt(1))(foo); // 'axolotl' set(barL)(7)(foo); set(bazAt(1))('potrzebie')(foo); console.log(foo); // { bar: 7, baz: ['quux','potrzebie'] } ``` Some key things to note: - Lenses are composable; and - Lenses can be used to both set *and* get parts of an object. More technically, a lens is a function of three arguments: 1. Some structure (eg an Object, an Array, etc); 2. An index specifying some piece of the structure; and 3. A function which transforms that piece The lens then returns a new structure, with its transformed piece inside. First, let's define some things we know we'll need, to wit: - A way to replace part of a structure at a certain key with a new value; - A way to compose functions. ```javascript if(!Function.prototype.compose) { Function.prototype.compose = function(g) { let f = this; return function(x) { return f(g(x)); }; }; } let replace = function(key, value, thing) { thing[key] = value; return thing; }; ``` Now, on to lenses. A lens breaks a part from a whole, passes the part to a function, and puts the result back into the whole. Intuitively, then, whether or not a lens is used as a setter or a getter rests on that function we pass in. This function we pass to the lens must return something we can put back in the whole -- or at the very least, lead us to something which fits the bill. My strategy will be to write two functions which **both** take a value and return an object with two properties: - a reference to the wrapped-up value; and - a function to let us transform that value. Some of you may have already guessed it, but I'm basically defining two functors: ```javascript let Setter = (x) => Object.seal({ value: x, map: (f) => Setter(f(x)) }); let Getter = (x) => Object.seal({ value: x, map: (f) => Getter(x) }); let extract = (s) => s.value; let setTest = Setter(5); console.log(extract(setTest.map((x) => x*2))); // 10 let getTest = Getter(5); console.log(extract(getTest.map((x) => x*2))); // 5 ``` The key here is uniformity: both Setter and Getter wrap up a value and allow me to at least pretend I'm transforming the value inside. Setter lets me do it; Getter doesn't. Simple. The uniform way to transform the value inside is the `map()` method. Armed with these two functions, we can finally write the lens function: ```javascript let lens = (key) => (fn) => (s) => fn(s[key]).map((v) => replace(key, v, s)); ``` Note that I'm writing this function in *curried* style. This is mostly for stylistic reasons but it does make the code more readable and usable later on. You'll see. So now we can create lenses! ```javascript let myself = { name: 'gatlin', age: 27, dogs: [{ name: 'louie', breed: 'pug' }, { name: 'pugzy', breed: 'pug' }] }; let nameL = lens('name'); // <-- this is why we curried the function let ageL = lens('age'); let dogsL = lens('dogs'); let dogAt = (idx) => dogsL.compose(lens(idx)); let rename = (thing, newName) => set(nameL)(newName)(thing); ``` So how do we *use* these fancy new lenses? Lets first tackle reading, or "getting", a value from a structure using a lens: ```javascript let get = (l) => (s) => extract.compose(l(Getter))(s); ``` This is a function which takes a lens and a structure. The `l` variable is a lens, which means that it is going to need a transformation function and then a value. so `l(Getter)` returns a function that needs a structure given to it (which would be `s`). However, ultimately we don't want a `Getter` object, we want the value inside it. So we compose `extract` with all this to retrieve the final value. As for setting, that's a special case of a more general operation: transforming a part of a whole. Replacement is one kind of transformation. For historical reasons let's define this more general kind of mutation as `over`: ```javascript let over = (l) => (f) => (s) => extract.compose(l(Setter.compose(f)))(s); ``` Let's unpack this. `over` requires a lens, a transformation function, and a structure. We compose `Setter` and our transformation function, creating a `Setter` containing the new value we want to put back in the whole. Then we pass this resulting function to our lens `l`, creating a new function asking for a structure. And again, as with `Getter`, we ultimately want the object back, not the `Setter` object, so we compose `extract` with all this. Pause and re-read this a few times if you need to; I'll wait. So actually *setting* a value is a special case of `over` where our transformation function takes the value we want to set, the original value, and returns the value we want to set. This function is called `constant`: ```javascript let constant = (x) => (y) => x; let set = (l) => (v) => (s) => over(l)(constant(v))(s); ``` Now let's try out our lenses! ```javascript console.log(myself); // "{ name: 'gatlin', age ... }" rename(myself, 'Gatlin'); console.log(get(nameL)(myself)); // "Gatlin" set( // Lenses compose! dogAt(0).compose(nameL) )('Louie')(myself); // And now the dog has a capitalized name // But we can also retrieve a part and use existing lens-based functions on them! let pugzy = get(dogAt(1))(myself); rename(pugzy,'Pugzy'); console.log(myself); // Run it for yourself! ``` Anyway that's the basics of lenses. There's a whole rabbit hole you can get lost in if you want with this, but this should be enough to interest you. File renamed without changes. -
gatlin revised this gist
Jun 30, 2016 . 1 changed file with 30 additions and 13 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -8,14 +8,39 @@ * * What is a lens? * * A lens in programming, much like a lens in the real world, allows you to * focus in on a small part of a larger whole and then do something with or to * that part. * * You may also think of it as breaking a piece out, manipulating the piece, * and then reconstructing the whole. * * That "something" can be changing the value or merely passing the value * elsewhere to be read. * * Thus, lenses are both getters *and* setters. Here's an example of the code * we'll be implementing: * * let foo = { * bar: 5, * baz: [ 'quux', 'axolotl' ] * }; * * let barL = lens('bar'); * let bazL = lens('baz'); * let bazAt = (index) => bazL.compose(lens(index)); * * let axolotl = get(bazAt(1))(foo); // 'axolotl' * set(barL)(7)(foo); * set(bazAt(1))('potrzebie')(foo); * * console.log(foo); // { bar: 7, baz: ['quux','potrzebie'] } * * Some key things to note: * * - Lenses are composable; and * - Lenses can be used to both set *and* get parts of an object. * * More technically, a lens is a function of three arguments: * * 1. Some structure (eg an Object, an Array, etc); @@ -24,14 +49,6 @@ * * The lens then returns a new structure, with its transformed piece inside. * * First, let's define some things we know we'll need, to wit: * * - A way to replace part of a structure at a certain key with a new value; @@ -184,4 +201,4 @@ set( let pugzy = get(dogAt(1))(myself); rename(pugzy,'Pugzy'); console.log(myself); -
gatlin renamed this gist
Jun 30, 2016 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
gatlin created this gist
Jun 30, 2016 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,187 @@ /* * (!) You can go to https://repl.it/languages/javascript, paste all this code * in there, and try it out for yourself! */ 'use strict'; /* * * What is a lens? * * A lens, basically, takes a structure and breaks out a part from the whole, * allowing you to do something with or to the part before reconstructing the * whole. Much like a tangible optical lens it lets you focus on some smaller * part of a larger object. * * That "something" can be changing the value or merely passing the value * elsewhere to be read. * * More technically, a lens is a function of three arguments: * * 1. Some structure (eg an Object, an Array, etc); * 2. An index specifying some piece of the structure; and * 3. A function which transforms that piece * * The lens then returns a new structure, with its transformed piece inside. * * Lenses have several very neat properties. * * First, a lens defined for part of some structure may be used as both a * setter and a getter. This means you only need to define a lens once to use * it for reading and writing. * * Because lenses are functions, they may be composed. * * First, let's define some things we know we'll need, to wit: * * - A way to replace part of a structure at a certain key with a new value; * - A way to compose functions. */ if(!Function.prototype.compose) { Function.prototype.compose = function(g) { let f = this; return function(x) { return f(g(x)); }; }; } let replace = function(key, value, thing) { thing[key] = value; return thing; }; /* * Now, on to lenses. A lens breaks a part from a whole, passes the part to a * function, and puts the result back into the whole. Intuitively, then, * whether or not a lens is used as a setter or a getter rests on that function * we pass in. * * This function we pass to the lens must return something we can put back in * the whole -- or at the very least, lead us to something which fits the bill. * * My strategy will be to write two functions which **both** take a value and * return an object with two properties: * * - a reference to the wrapped-up value; and * - a function to let us transform that value. * * Some of you may have already guessed it, but I'm basically defining two * functors: */ let Setter = (x) => Object.seal({ value: x, map: (f) => Setter(f(x)) }); let Getter = (x) => Object.seal({ value: x, map: (f) => Getter(x) }); let extract = (s) => s.value; let setTest = Setter(5); console.log(extract(setTest.map((x) => x*2))); // 10 let getTest = Getter(5); console.log(extract(getTest.map((x) => x*2))); // 5 /* * The key here is uniformity: both Setter and Getter wrap up a value and allow * me to at least pretend I'm transforming the value inside. Setter lets me do * it; Getter doesn't. Simple. * * The uniform way to transform the value inside is the `map()` method. * * Armed with these two functions, we can finally write the lens function: */ let lens = (key) => (fn) => (s) => fn(s[key]).map((v) => replace(key, v, s)); /* * Note that I'm writing this function in *curried* style. This is mostly for * stylistic reasons but it does make the code more readable and usable later * on. You'll see. * * So now we can create lenses! */ let myself = { name: 'gatlin', age: 27, dogs: [{ name: 'louie', breed: 'pug' }, { name: 'pugzy', breed: 'pug' }] }; let nameL = lens('name'); // <-- this is why we curried the function let ageL = lens('age'); let dogsL = lens('dogs'); let dogAt = (idx) => dogsL.compose(lens(idx)); let rename = (thing, newName) => set(nameL)(newName)(thing); /* * So how do we *use* these fancy new lenses? Lets first tackle reading, or * "getting", a value from a structure using a lens: */ let get = (l) => (s) => extract.compose(l(Getter))(s); /* * This is a function which takes a lens and a structure. The `l` variable is a * lens, which means that it is going to need a transformation function and * then a value. so `l(Getter)` returns a function that needs a structure given * to it (which would be `s`). However, ultimately we don't want a `Getter` * object, we want the value inside it. So we compose `extract` with all this * to retrieve the final value. * * As for setting, that's a special case of a more general operation: * transforming a part of a whole. Replacement is one kind of transformation. * For historical reasons let's define this more general kind of mutation as * `over`: */ let over = (l) => (f) => (s) => extract.compose(l(Setter.compose(f)))(s); /* * Let's unpack this. `over` requires a lens, a transformation function, and a * structure. We compose `Setter` and our transformation function, creating a * `Setter` containing the new value we want to put back in the whole. Then we * pass this resulting function to our lens `l`, creating a new function asking * for a structure. And again, as with `Getter`, we ultimately want the object * back, not the `Setter` object, so we compose `extract` with all this. * * Pause and re-read this a few times if you need to; I'll wait. * * So actually *setting* a value is a special case of `over` where our * transformation function takes the value we want to set, the original value, * and returns the value we want to set. This function is called `constant`: */ let constant = (x) => (y) => x; let set = (l) => (v) => (s) => over(l)(constant(v))(s); /* * Now let's try out our lenses! */ console.log(myself); rename(myself, 'Gatlin'); console.log(get(nameL)(myself)); // Lenses compose! set( dogAt(0).compose(nameL) )('Louie')(myself); // But we can also retrieve a part and use existing lens-based functions on them! let pugzy = get(dogAt(1))(myself); rename(pugzy,'Pugzy'); console.log(myself);