Lenses ou plutôt dit getters et combinaisons combinables

Cette fois, nous découvrirons ce que sont la lentille (objectifs en anglais), comment ils regardent JavaScript et j’espère qu’à la fin de tout cela, nous pouvons créer une implémentation presque adéquate.

Mais d’abord revenons un peu et nous allons nous demander.

Qu’est-ce que getter et setter?

sont des fonctions Cela doit être fait dans un but, extraire ou attribuer une valeur. Mais bien sûr que ce n’est pas la seule chose qu’ils peuvent faire. Dans la plupart des cas (que j’ai vu) sont utilisés pour observer des modifications à une variable et causer un effet ou de placer des validations qui empêchent tout comportement indésirable.

en JavaScript, ils peuvent être explicites.

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'

ou peut être implicite.

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'

Mais qu’est-ce que cela a une mauvaise chose que certaines personnes ressentent la nécessité d’utiliser des alternatives telles que les laes?

commençons par le deuxième exemple. Je peux dire à certaines personnes qu’ils n’aiment pas les choses magiques, il suffit d’avoir une fonction qui a fonctionné sans leur connaissance est suffisante pour les éviter.

Le premier exemple est plus intéressant. Revenons-y à nouveau.

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

exécuter get_thing Le résultat est le résultat stuff, jusqu’à présent, d’accord. Mais voici le problème, quand vous l’utilisez à nouveau et de la même manière, vous obtenez other stuff. Vous devez suivre le dernier appel à set_thing pour savoir ce que vous obtiendrez. Vous n’avez pas la capacité de prêcher le résultat de get_thing, vous ne pouvez pas être sûr à 100% sans regarder (ou savoir) d’autres parties du code.

Y a-t-il une alternative meilleure?

Je ne dirais pas mieux. Essayons de créer ces lentilles, puis vous pouvez décider si vous l’aimez ou non.

De quoi avons-nous besoin? La lentille est un concept qui est dans le paradigme de la programmation fonctionnelle, puis la première chose que nous ferons créera des fonctions auxiliaires. Ce seront notre première version de getter and Setter.

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

maintenant le « constructeur »

iv id = « /div>

remarquera que Lens ne fait pas absolument rien, c’est exprès. Puisque vous pouvez réaliser que la plupart des travaux sont dans getter et setter. Le résultat sera aussi efficace que ses implémentations de getter et setter.

maintenant, pour faire un lens Faites quelque chose d’utile, nous allons créer trois fonctions.

view: extraire une valeur.

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

: Transformez une valeur à l’aide d’un rappel.

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

set: Remplacez une valeur

est temps de créer des tests.

disons que nous avons un objet appelé alice .

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

Commençons par quelque chose de simple, nous allons inspecter une valeur. Nous devrions faire cela.

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

Je vois qu’ils ne sont pas impressionnés et c’est bien. Je viens d’écrire beaucoup de choses juste pour voir un nom. Mais c’est la matière, tout ce qui sont des fonctions isolées. Nous avons toujours la possibilité de combiner et de créer de nouveaux. Commençons par Lens(prop, assoc), utilisons-le très souvent.

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

et maintenant …

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

peut même aller plus loin et créer une fonction accepte uniquement l’objet qu’il contient les données.

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`

suffisante. Revenons à nos tests. Nous allons avec , nous transformerons le texte en 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"

est le tour de set.

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

Très gentil maisnameest juste une propriété, que se passe-t-il avec les objets imbriqués ou les arrangements? Eh bien, c’est là que notre mise en œuvre devient quelque chose de mal à l’aise. En ce moment, nous devrions faire quelque chose comme ça.

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"

Je les écoute. Ne vous inquiétez pas, je ne les laisserais pas écrire des choses comme ça. C’est à cause de choses que certains vont et disent « USA Ramda et déjà » (et ont raison), mais qu’est-ce que Ramda le rend si spécial?

Touche spéciale

Ils vont à La documentation Ramda et rechercher « Lens » verront qu’ils ont une fonction appelée lensProp qui fait fondamentalement la même chose que Lprop. Et s’ils vont au code source, vous le verrez.

regarde ça.Maintenant, les commentaires du code et de la documentation suggèrent que vous travaillez avec une seule propriété. Revenons à notre recherche dans votre documentation. Maintenant, faisons attention à cette fonction curieuse appelée lensPath. Il semble qu’il fait exactement ce que nous voulons. Encore une fois, nous voyons le code source et que voyons-nous?

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

Le secret est dans d’autres fonctions qui font pas de lien spécifique avec lenses. N’est-ce pas génial?

Qu’est-ce qu’il y a dans cette fonction path? Allons vérifier. Je vais vous montrer une version légèrement différente, mais le comportement est le même.

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 Faites la même chose avec assocPath. Dans ce cas à Ramda, utilisez des fonctions internes mais en substance, c’est ce qui se passe.

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

avec nos nouvelles connaissances Nous pouvons créer Lpath et améliorer Lprop

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

Nous pouvons faire d’autres choses, telles que la manipulation de la propriété pets de 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'

Tout fonctionne bien, mais il y a un petit détail, notre constructeur Lens ne produit pas « des instances combinables « . Imaginez que nous avons des laes à plusieurs endroits et que nous voulons les combiner comme suit.

qui ne fonctionnerait pas parce que compose Attendez une liste de fonctions et ce que nous avons maintenant sont des objets. Mais nous pouvons changer cela (de manière très curieuse) avec quelques astuces de programmation fonctionnelle.

Commençons par le constructeur. Au lieu de retourner un objet, nous retournerons une fonction qui reçoit « par » un rappel, un objet et qui renvoie une fonction (c’est une chose qui a une méthode map que Suivez ces règles)

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

et celle de fn => obj => Quoi? Cela nous aidera avec le problème que nous avons avec compose. Après que vous vous fournisez getter et

setter

retourne une fonction compatible aveccompose.

et functor.map? C’est-à-dire que nous pouvons utiliser une lentille sous forme d’unité (comme Lprop('pets')) et aussi dans le cadre d’une chaîne utilisant compose.

Si vous vous demandez quelle différence il y a avec ce que Ramda fait avec ce que Ramda fait, ils utilisent leur propre implémentation de la fonction map.

MODIFIE view et . Commençant par view.

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

cette fonction constant peut sembler inutile complexe mais il a son but. Les choses peuvent être enchevêtrées lorsque vous utilisez compose, cette structure est assurée que la valeur que nous voulons rester intacte.

et ? C’est presque la même chose, sauf que dans ce cas, nous utilisons la fonction 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;}

Et maintenant, nous devrions avoir une mise en œuvre presque adéquate. C’est ce que nous avons sans compter les dépendances (path et 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;}

croire si je leur dis que ça marche? Ne devraient pas. Faisons des tests. Revenons avec alice et nous allons ajouter un autre objet, 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: };

Et parce que nous avions tout ce qui était prévu d’avant, nous avons déjà des lentilles disponibles.

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

supposons que nous voulions manipuler la propriété dragon de chacun, tout ce que nous avons à faire est de combiner.

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"

Nous venons de manipuler un objet imbriqué à plusieurs niveaux combinant la lentille. Nous avons résolu un problème combinant des fonctions. Si vous ne le pensez pas, je ne sais pas quoi de leur dire d’autre.

Ces choses sont difficiles à vendre car elles nécessitent un style particulier pour pouvoir tirer parti d’eux au maximum. Et pour ceux qui utilisent JavaScript, il y a probablement une bibliothèque qui résout le même problème, mais de manière plus pratique ou au moins, elle convient à leur style.

Quoi qu’il en soit, s’ils sont toujours intéressés par la manière dont ils seraient Travailler ces lentilles dans un contexte plus complexe Review Ce référentiel, est un exemple d’application « Real World » (quelque chose comme un clone de moyenne.com) utilise HyperApp pour gérer l’interface. L’auteur souhaitait utiliser la lentille pour gérer le statut de l’application.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *