Lensses o piuttosto detti getter e setter combinabili

Questa volta scopriremo cosa sono lenti (lenti in inglese), come guardano a JavaScript e spero che alla fine di tutto questo possiamo creare un’implementazione quasi adeguata.

Ma prima torniamo indietro un po ‘e ci chiediamo a noi stessi.

Cosa sono getter e setter?

Le funzioni sono funzioni Questo deve essere fatto per uno scopo, estrarre o assegnare un valore. Ma ovviamente non è l’unica cosa che possono fare. Nella maggior parte dei casi (che ho visto) vengono utilizzati per osservare le modifiche a una variabile e causare qualche effetto o di effettuare convalida che impediscono qualsiasi comportamento indesiderato.

in JavaScript può essere esplicito.

function Some() { let thing = 'stuff'; return { get_thing() { // puedes hacer lo que sea aquí return thing; }, set_thing(value) { // igual aquí thing = value; } }}let obj = Some();obj.get_thing(); // => 'stuff'obj.set_thing('other stuff');obj.get_thing(); // => 'other stuff'

o può essere implicito.

let some = {};Object.defineProperty(some, 'thing', { get() { return 'thing'; }, set(value) { console.log("no pasarás"); }});some.thing // => 'thing'some.thing = 'what?';//// no pasarás//some.thing // => 'thing'

Ma cosa fa una cosa brutta che alcune persone sentono la necessità di usare alternative come le lese?

Iniziamo con il secondo esempio. Posso dire ad alcune persone a cui non piacciono le cose magiche, solo avere una funzione che è stata corretta senza che le loro conoscenze siano sufficienti per evitarle.

Il primo esempio è più interessante. Vediamo di nuovo.

obj.get_thing(); // => 'stuff'obj.set_thing('other stuff');obj.get_thing(); // => 'other stuff'

run get_thing Il risultato è stuff, finora tutto bene. Ma qui arriva il problema, quando lo usi di nuovo e nello stesso modo in cui ottieni other stuff. Devi tenere traccia dell’ultima chiamata a set_thing per sapere cosa otterrai. Non hai la possibilità di predicare il risultato di get_thing, non è possibile essere sicuri al 100% senza guardare (o conoscere) altre parti del codice.

C’è un’alternativa migliore?

Non direi di meglio. Proviamo a creare questi obiettivi, allora puoi decidere se ti piace o no.

Di cosa abbiamo bisogno? L’obiettivo è un concetto che si trova nel paradigma della programmazione funzionale, quindi la prima cosa che faremo creeremo funzioni ausiliarie. Queste saranno la nostra prima versione di Getter e Setter.

// Getterfunction prop(key) { return obj => obj;}// Setterfunction assoc(key) { return (val, obj) => Object.assign({}, obj, {: val});}

Ora il “costruttore”

iv id = “/div>

noterà che Lens non fa assolutamente nulla, questo è apposta. Dato che puoi capire che la maggior parte del lavoro è in getter e setter. Il risultato sarà efficiente come le sue implementazioni di getter e setter.

Ora, per creare un

Oralens

Fai qualcosa di utile Creeremo tre funzioni.

view: Estrarre un valore.

Estrarre un valore.

function view(lens, obj) { return lens.getter(obj);}

over: trasformare un valore utilizzando un callback.

function over(lens, fn, obj) { return lens.setter( fn(lens.getter(obj)), obj );}

set: Sostituisci un valore

function always(val) { return () => val;}function set(lens, val, obj) { // no es genial? Ya estamos reusando funciones return over(lens, always(val), obj);}

è il momento di creare alcuni test.

diciamo che abbiamo un oggetto chiamato alice .

const alice = { name: 'Alice Jones', address: , pets: { dog: 'joker', cat: 'batman' }};

Iniziamo con qualcosa di semplice, ispezioniamo un valore. Dovremmo farlo.

const result = view( Lens(prop('name'), assoc('name')), alice);result // => "Alice Jones"

Vedo che non sono impressionati e va bene. Ho appena scritto un sacco di cose solo per vedere un nome. Ma questa è la questione, tutte le funzioni isolate. Abbiamo sempre la possibilità di combinare e crearne di nuovi. Iniziamo con Lens(prop, assoc), usiamolo molto spesso.

function Lprop(key) { return Lens(prop(key), assoc(key));}

e ora …

const result = view(Lprop('name'), alice);result // => "Alice Jones"

Può persino andare oltre e creare una funzione che accetta solo l’oggetto che contiene i dati.

const get_name = obj => view(Lprop('name'), obj);// o con aplicación parcialconst get_name = view.bind(null, Lprop('name'));// o usando una dependencia.// view = curry(view);const get_name = view(Lprop('name'));// y lo mismo aplica para `set` y `over`

sufficiente. Torniamo ai nostri test. Andiamo con over, trasformeremo il testo in maiuscolo.

const upper = str => str.toUpperCase();const uppercase_alice = over(Lprop('name'), upper, alice);// vieron lo que hice?get_name(uppercase_alice) // => "ALICE JONES"// por si acasoget_name(alice) // => "Alice Jones"

è il turno di set.

const alice_smith = set(Lprop('name'), 'Alice smith', alice);get_name(alice_smith) // => "Alice smith"// por si acasoget_name(alice) // => "Alice Jones"

Tutto Molto bello ma name è solo una proprietà, cosa succede con gli oggetti nidificati o gli accordi? Bene, è qui che la nostra implementazione diventa qualcosa di scomodo. In questo momento dovremmo fare qualcosa del genere.

let dog = Lens( obj => prop('dog')(prop('pets')(obj)), obj => assoc('dog')(assoc('pets')(obj)));view(dog, alice); // => "joker"// o traemos una dependencia, `compose`dog = Lens( compose(prop("dog"), prop("pets")), compose(assoc("dog"), assoc("pets")));view(dog, alice); // => "joker"

Ascolto loro. Non preoccuparti, non li lascerei scrivere cose del genere. È a causa delle cose che alcuni vanno e dicono “USA Ramda e già” (e sono giusti), ma cosa fa Ramda lo renderà così speciale?

il tocco speciale

vanno a La documentazione di Ramda e cercare “lente” vedrà che hanno una funzione chiamata lensProp che fondamentalmente fa lo stesso di Lprop. E se vadano al codice sorgente vedrai questo.

function lensProp(k) { return lens(prop(k), assoc(k));}

Guardalo.Ora, i commenti nel codice e la documentazione suggeriscono di lavorare con una singola proprietà. Torniamo alla nostra ricerca nella tua documentazione. Ora prestiamo attenzione a quella funzione curiosa chiamata lensPath. Sembra che faccia esattamente ciò che vogliamo. Ancora una volta vediamo il codice sorgente e cosa vediamo?

function lensPath(p) { return lens(path(p), assocPath(p));}// Bienvenidos al paradigma funcional

Il segreto è in altre funzioni che fanno Non non ha alcun link specifico con lenses. Non è eccezionale?

Cosa c’è in quella funzione path? Controlliamo. Ti mostrerò una versione leggermente diversa, ma il comportamento è lo stesso.

I ‘ Fare lo stesso con assocPath. In questo caso, a Ramda, usa alcune funzioni interne ma in sostanza questo è ciò che accade.

function assocPath(path, value, obj) { // otra vez esto // por eso tienen la función `curry` if (arguments.length === 1) { return assocPath.bind(this, path); } else if (arguments.length === 2) { return assocPath.bind(this, path, value); } // revisamos si está vacío if (path.length === 0) { return value; } var index = path; // Cuidado: recursividad adelante if (path.length > 1) { var is_empty = typeof obj !== 'object' || obj === null || !obj.hasOwnProperty(index); // si el objeto actual está "vacío" // tenemos que crear otro // de lo contrario usamos el valor en `index` var next = is_empty ? typeof path === 'number' ? : {} : obj; // empecemos otra vez // pero ahora con un `path` reducido // y `next` es el nuevo `obj` value = assocPath(Array.prototype.slice.call(path, 1), value, next); } // el caso base // o copiamos un arreglo o un objeto if (typeof index === 'number' && Array.isArray(obj)) { // 'copiamos' el arreglo var arr = .concat(obj); arr = value; return arr; } else { // una copia como las de antes var result = {}; for (var p in obj) { result = obj; } result = value; return result; }}

Con la nostra nuova conoscenza Possiamo creare Lpath e migliorare Lprop.

function Lpath(keys) { return Lens(path(keys), assocPath(keys));}function Lprop(key) { return Lens(path(), assocPath());}

/ Div>

Ora possiamo fare altre cose, come a manipolare la proprietà pets di alice.

const dog_lens = Lpath();view(dog_lens, alice); // => 'joker'let new_alice = over(dog_lens, upper, alice);view(dog_lens, new_alice); // => 'JOKER'new_alice = set(dog_lens, 'Joker', alice);view(dog_lens, new_alice); // => 'Joker'

Tutto funziona grande ma c’è un piccolo dettaglio, il nostro costruttore Lens non produce “istanze combinabili “. Immagina di avere le lese in diversi posti e vogliamo combinarli come segue.

che non funzionerebbe perché compose Attendi un elenco di funzioni e ciò che abbiamo ora sono oggetti. Ma possiamo cambiarlo (in modo molto curioso) con alcuni trucchi di programmazione funzionale.

Iniziamo con il costruttore. Invece di restituire un oggetto, restituiremo una funzione, uno che riceve “da” un callback, un oggetto e questo restituisce una funzione (questa è una cosa che ha un metodo map Segui queste regole)

function Lens(getter, setter) { return fn => obj => { const apply = focus => setter(focus, obj); const functor = fn(getter(obj)); return functor.map(apply); };}

e quello di fn => obj => Cosa? Questo ci aiuterà con il problema che abbiamo con compose. Dopo aver fornito getter e setter restituisce una funzione compatibile con compose.

e functor.map? Ciò è quello di assicurarsi che possiamo usare un obiettivo come unità (come Lprop('pets')) e anche come parte di una stringa usando compose.

Nel caso in cui ti chiedi quale differenza c’è con ciò che fa Ramda, usano la propria implementazione della funzione map.

Ora modifica view e over. A partire da .

function view(lens, obj) { const constant = value => ({ value, map: () => constant(value) }); return lens(constant)(obj).value;}

Quella funzione può sembrare inodatico complesso ma ha il suo scopo. Le cose possono essere impigliate molto quando usi compose, quella struttura è assicurata che il valore che vogliamo rimanere intatto.

e over? È quasi la stessa cosa, tranne che in tal caso utilizziamo la funzione setter.

function over(lens, fn, obj) { const identity = value => ({ value, map: setter => identity(setter(value)) }); const apply = val => identity(fn(val)); return lens(apply)(obj).value;}

E ora dovremmo avere un’implementazione quasi adeguata. Questo è ciò che abbiamo senza contare le dipendenze (path e assocPath).

function Lens(getter, setter) { return fn => obj => { const apply = focus => setter(focus, obj); const functor = fn(getter(obj)); return functor.map(apply); };}function view(lens, obj) { const constant = value => ({ value, map: () => constant(value) }); return lens(constant)(obj).value;}function over(lens, fn, obj) { const identity = value => ({ value, map: setter => identity(setter(value)) }); const apply = val => identity(fn(val)); return lens(apply)(obj).value;}function set(lens, val, obj) { return over(lens, always(val), obj);}function Lprop(key) { return Lens(path(), assocPath());}function Lpath(keys) { return Lens(path(keys), assocPath(keys));}function always(val) { return () => val;}

crederesti se dico loro funziona? Non dovresti. Facciamo alcuni test. Torniamo con alice e aggiungeremo un altro oggetto, calie.

const alice = { name: "Alice Jones", address: , pets: { dog: "joker", cat: "batman", imaginary: { dragon: "harley" } }};const calie = { name: "calie Jones", address: , pets: { dog: "riddler", cat: "ivy", imaginary: { dragon: "hush" } }, friend: };

E poiché avevamo tutto programmato da prima, abbiamo già qualche obiettivo disponibile.

// uno genéricoconst head_lens = Lprop(0);// otros específicosconst bff_lens = compose(Lprop('friend'), head_lens); const imaginary_lens = Lpath();

Supponiamo che vogliamo manipolare la proprietà dragon da ciascuno, tutto ciò che dobbiamo fare è combinare.

const dragon_lens = compose(imaginary_lens, Lprop('dragon'));// sólo porque síconst bff_dragon_lens = compose(bff_lens, dragon_lens); // democonst upper = str => str.toUpperCase();// viewview(dragon_lens, calie); // => "hush"view(bff_dragon_lens, calie); // => "harley"// overlet new_calie = over(dragon_lens, upper, calie);view(dragon_lens, new_calie); // => "HUSH"new_calie = over(bff_dragon_lens, upper, calie);view(bff_dragon_lens, new_calie); // => "HARLEY"// setnew_calie = set(dragon_lens, 'fluffykins', calie);view(dragon_lens, new_calie); // => "fluffykins"new_calie = set(bff_dragon_lens, 'pumpkin', calie);view(bff_dragon_lens, new_calie); // => "pumpkin"

Quindi abbiamo appena manipolato un oggetto nidificato a più livelli che combinano lente. Abbiamo risolto un problema che combina funzioni. Se non pensi bene, non so cos’altro dire loro.

Queste cose sono difficili da vendere perché richiedono uno stile particolare per poter sfruttare loro al massimo. E per coloro che usano JavaScript, probabilmente c’è una biblioteca che risolve lo stesso problema ma in un modo più conveniente o almeno si inserisce nel loro stile.

Comunque, se sono ancora interessati a come lo farebbero Lavora a questi obiettivi in una revisione del contesto più complessa Questo repository è un esempio di “Real World App” (qualcosa come un clone di Metal.com) utilizza HyperApp per gestire l’interfaccia. L’autore voleva usare la lente per gestire lo stato dell’applicazione.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *