Lensses sau mai degrabă a spus getters și settere combinate

De acest moment vom descoperi ceea ce sunt lentilele (lentile în limba engleză), cum se uită la JavaScript și sper că la sfârșitul tuturor acest lucru putem crea o implementare aproape adecvată.

Dar mai întâi să ne întoarcem puțin și o să ne întrebăm.

Ce sunt Getter și Setter?

sunt funcții Acest lucru trebuie făcut pentru un scop, extrageți sau atribuiți o valoare. Dar, desigur, că nu este singurul lucru pe care îl pot face. În cele mai multe cazuri (pe care le-am văzut) sunt folosite pentru a observa modificări la o variabilă și pentru a determina un efect sau pentru a plasa validații care împiedică orice comportament nedorit.

în JavaScript pot fi explicit.

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'

sau poate fi implicit.

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'

Dar ceea ce are un lucru rău că unii oameni simt nevoia de a folosi alternative ca leses?

Să începem cu al doilea exemplu. Pot spune unor oameni că nu-i plac lucrurile magice, având doar o funcție care a funcționat fără cunoașterea lor este suficientă pentru a le evita.

Primul exemplu este mai interesant. Să o vedem din nou.

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

run get_thing Rezultatul este stuff, până acum bine. Dar aici vine problema, când o folosiți din nou și în același mod veți obține other stuff. Trebuie să urmăriți ultimul apel către set_thing pentru a afla ce veți obține. Nu aveți capacitatea de a predica rezultatul get_thing, nu puteți fi 100% sigur fără a căuta (sau știi) alte părți ale codului.

Există o alternativă mai bună?

Nu aș spune mai bine. Să încercăm să creăm aceste lentile, apoi puteți decide dacă vă place sau nu.

Ce avem nevoie? Lentila sunt un concept care se află în paradigma programării funcționale, apoi primul lucru pe care îl vom face va crea funcții auxiliare. Acestea vor fi prima noastră versiune de Getter și Setter.

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

acum „constructor”

div id = „/div>

eoarece vă puteți da seama că cea mai mare parte a lucrării este îngetterșisetter. Rezultatul va fi la fel de eficient ca și implementările sale degetterșisetter.

acum, pentru a face un lens Faceți ceva util Vom crea trei funcții.

view: extrageți o valoare.

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

over: Transformați o valoare utilizând un apel invers.

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

set: Înlocuiți o valoare

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

este timpul pentru a crea unele teste.

hai să spunem că avem un obiect numit alice .

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

Să începem cu ceva simplu, vom inspecta o valoare. Va trebui să facem acest lucru.

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

Văd că nu sunt impresionați și asta e bine. Tocmai am scris o mulțime de lucruri doar pentru a vedea un nume. Dar aceasta este problema, toate acestea sunt funcții izolate. Întotdeauna avem opțiunea de a combina și de a crea noi. Să începem cu Lens(prop, assoc), să o folosim foarte des.

Div>

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

și acum …

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

Poate merge chiar mai departe și poate crea o funcție care acceptă numai obiectul pe care îl conține datele.

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`

suficient. Să ne întoarcem la testele noastre. Mergem cu over, vom transforma textul la majuscule.

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"

este rândul său de set.

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

Toate Foarte frumos, dar name este doar o proprietate, ce se întâmplă cu obiectele sau aranjamentele imbricate? Ei bine, acolo implementarea noastră devine ceva inconfortabil. Chiar acum ar trebui să facem așa ceva.

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"

Le ascult. Nu vă faceți griji, nu le-aș lăsa să scrie lucruri de genul asta. Din cauza lucrurilor pe care unele merg și spun „SUA Ramda și deja” (și au dreptate), dar ceea ce face Ramda atât de specială?

Atingerea specială

ei merg la Documentația RAMDA și căutați „lentilă” va vedea că au o funcție numită lensProp care practic face același lucru cu Lprop. Și dacă merg la codul sursă, veți vedea acest lucru.

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

uite la asta.Acum, comentariile din Cod și documentația sugerează că lucrați cu o singură proprietate. Să ne întoarcem la căutarea noastră în documentația dvs. Acum, să acordăm atenție acelei funcții curioase numite lensPath. Se pare că face exact ceea ce vrem. Încă o dată vedem codul sursă și ce vedem?

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

Secretul este în alte funcții care fac Nu aveți nicio legătură specifică cu lenses. Nu este grozav?

Ce este acolo în acea funcție path? Sa verificam. Îți voi arăta o versiune puțin diferită, dar comportamentul este același.

function path(keys, obj) { if (arguments.length === 1) { // esto es para imitar la dependencia `curry` // esto es lo que pasa // retornan una función que recuerda `keys` // y espera el argumento `obj` return path.bind(this, keys); } var result = obj; var idx = 0; while (idx < keys.length) { // no nos agrada null if (result == null) { return; } // así obtenemos los objetos anidados result = result]; idx += 1; } return result;}

i ‘ LL face același lucru cu assocPath. În acest caz, în Ramda, utilizați câteva funcții interne, dar, în esență, acest lucru se întâmplă.

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; }}

cu noile noastre cunoștințe Putem crea Lpath și îmbunătățiți Lprop.

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

Acum putem face alte lucruri, cum ar fi manipularea proprietății pets al 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'

Totul funcționează excelent, dar există un detaliu mic, constructorul nostru Lens nu produce „instanțe combinate „. Imaginați-vă că avem levere în mai multe locuri și vrem să le combinăm după cum urmează.

iv id = „866ea3005”

care nu ar funcționa pentru că compose Așteptați o listă de funcții și ceea ce avem acum sunt obiecte. Dar putem schimba acest lucru (într-un mod foarte curios) cu câteva trucuri de programare funcțională.

Să începem cu constructorul. În loc să returnați un obiect, vom returna o funcție, una care primește „de” un apel invers, un obiect și care returnează o funcție (adică un lucru care are o metodă map Urmați aceste reguli)

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

și de fn => obj => Ce? Acest lucru ne va ajuta cu problema pe care o avem cu compose. După ce vă furnizați getter și setter Returnează o funcție compatibilă cu compose.

și functor.map? Acest lucru este de a vă asigura că putem folosi o lentilă ca o unitate (ca Lprop('pets')) și, de asemenea, ca parte a unui șir folosind compose.

În cazul în care vă întrebați ce diferență există cu ceea ce face Ramda, ei folosesc propria lor implementare a funcției map.

Modificați acum iv id = „0606089731” și over. Începând cu view.

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

acea funcție constant poate părea un total complex, dar are scopul său. Lucrurile pot fi încurcate mult când utilizați compose, această structură este asigurată că valoarea pe care o dorim să rămânem intactă.

și over? Este aproape același, cu excepția faptului că, în acest caz, folosim funcția 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;}

și acum ar trebui să avem o implementare aproape adecvată. Aceasta este ceea ce avem fără a număra dependențele (path și assocPath

div>

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;}

ar crede dacă le spun că funcționează? Tu nu ar trebui să. Să facem niște teste. Să ne întoarcem cu alice și vom adăuga un alt obiect, 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: };

și pentru că am avut totul planificat înainte, avem deja niște lentile disponibile.

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

presupunem să manipuleze proprietatea dragon din fiecare, tot ce trebuie să facem este să combinăm.

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"

Așadar, am manipulat un obiect imbricat la mai multe niveluri care combină lentila. Am rezolvat o problemă de combinare a problemelor. Dacă nu credeți că este minunat, nu știu ce altceva să le spun.

Aceste lucruri sunt dificil de vândut, deoarece acestea necesită un anumit stil pentru a putea profita de ei la maxim. Și pentru cei care folosesc JavaScript, probabil că există o bibliotecă care rezolvă aceeași problemă, dar într-un mod mai convenabil sau cel puțin se potrivește în stilul lor.

Oricum, dacă sunt încă interesați de modul în care vor fi Lucrați aceste lentile într-un context mai complex Revizuirea acestui depozit, este un exemplu de „Real World App” (ceva de genul unei clone de mediu.com) utilizează hyperapp pentru a gestiona interfața. Autorul a vrut să utilizeze obiectivul pentru a gestiona starea aplicației.

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *