Lentes ou sim disse getters e setters combináveis

desta vez vamos descobrir o que são lentes (lentes em inglês), como eles olham para Javascript e espero que no final de tudo isso possamos criar uma implementação quase adequada.

Mas primeiro vamos voltar um pouco e vamos nos perguntar.

O que são getter e setter?

são funções Isso deve ser feito para um propósito, extrair ou atribuir um valor. Mas é claro que não é a única coisa que eles podem fazer. Na maioria dos casos (que eu tenho visto) são usados para observar alterações em uma variável e causar alguns efeitos ou para colocar validações que impedem qualquer comportamento indesejado.

em javascript eles podem ser explícitos.

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 pode ser implícito.

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'

Mas o que isso tem uma coisa ruim que algumas pessoas sentem a necessidade de usar alternativas como leses?

Vamos começar com o segundo exemplo. Eu posso dizer algumas pessoas que eles não gostam de coisas mágicas, apenas ter uma função que está correndo sem o seu conhecimento é suficiente para evitá-los.

O primeiro exemplo é mais interessante. Vamos ver de novo.

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

Executar get_thing O resultado é stuff Até agora tudo bem. Mas aqui vem o problema, quando você o usa de novo e da mesma maneira que recebe other stuff. Você tem que acompanhar a última chamada para set_thing para saber o que você receberá. Você não tem a capacidade de pregar o resultado de get_thing, você não pode ter 100% de certeza sem olhar (ou saber) outras partes do código.

Existe uma alternativa melhor?

Eu não diria melhor. Vamos tentar criar essas lentes, então você pode decidir se você gosta ou não.

O que precisamos? Lente é um conceito que está no paradigma da programação funcional, então a primeira coisa que faremos criará funções auxiliares. Estes serão nossa primeira versão do Getter e Setter.

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

Agora o “construtor”

iv id = “

irá notar que Lens não faz absolutamente nada, isso é de propósito. Como você pode perceber que a maior parte do trabalho está em getter e . O resultado será tão eficiente quanto suas implementações de getter e .

agora, para fazer uma lens Faça algo útil, vamos criar três funções.

view: extrair um valor.

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

: Transforme um valor usando um retorno de chamada.

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

set: substitua um valor

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

É hora de criar alguns testes.

digamos que temos um objeto chamado alice .

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

Vamos começar com algo simples, vamos inspecionar um valor. Teríamos que fazer isso.

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

Eu vejo que eles não estão impressionados e tudo bem. Acabei de escrever muitas coisas apenas para ver um nome. Mas este é o assunto, tudo o que são funções isoladas. Nós sempre temos a opção de combinar e criar novos. Vamos começar com Lens(prop, assoc) Vamos usá-lo com muita frequência.

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

e agora …

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

Pode até mesmo ir mais longe e criar uma função que só aceita o objeto contém os dados.

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`

suficiente. Vamos voltar aos nossos testes. Nós vamos com vamos transformar o texto para maiúsculas.

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"

é a vez de set

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

todos Muito bom, mas name é apenas uma propriedade, o que acontece com os objetos ou arranjos aninhados? Bem, é aí que nossa implementação se torna algo desconfortável. Agora teríamos que fazer algo parecido.

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"

Eu os ouço. Não se preocupe, eu não os deixaria escrever coisas assim. É por causa de coisas que algumas vão e dizem “EUA Ramda e já” (e estão certos), mas o que Ramda torna tão especial?

o toque especial

eles vão para A documentação Ramda e procurar “lente” verá que eles têm uma função chamada lensProp que basicamente faz o mesmo que Lprop. E se eles vão para o código-fonte, você verá isso.

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

olhe para isso.Agora, os comentários no código e a documentação sugerem que você trabalha com uma única propriedade. Vamos voltar à nossa pesquisa em sua documentação. Agora vamos prestar atenção a essa função curiosa chamada lensPath. Parece que ele faz exatamente o que queremos. Mais uma vez vemos o código-fonte e o que vemos?

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

o segredo é em outras funções que fazem Não tem nenhum link específico com lenses. Não é ótimo?

O que há nessa função path? Vamos checar. Eu vou te mostrar uma versão ligeiramente diferente, mas o comportamento é o mesmo.

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 ‘ lerá o mesmo com assocPath. Nesse caso, em Ramda use algumas funções internas, mas em essência é o que acontece.

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

Com nosso novo conhecimento Podemos criar Lpath e melhorar Lprop.

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

Agora podemos fazer outras coisas, como manipular a propriedade 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'

Tudo funciona muito bem, mas há um pequeno detalhe, nosso construtor Lens não produz “instâncias combináveis . Imagine que temos leses em vários lugares e queremos combiná-los da seguinte forma.

Isso não funcionaria porque compose Aguarde uma lista de funções e o que temos agora são objetos. Mas podemos mudar isso (de uma maneira muito curiosa) com alguns truques de programação funcional.

Vamos começar com o construtor. Em vez de retornar um objeto, retornaremos uma função, uma que receba “por” um retorno de chamada, um objeto e que retorna uma função (que é uma coisa que tem um método map Siga estas regras)

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

e o de fn => obj => O que? Isso nos ajudará com o problema que temos com compose. Depois de fornecer você getter e setter retorna uma função compatível com compose.

e functor.map? Isso é para garantir que possamos usar uma lente como uma unidade (como Lprop('pets')) e também como parte de uma string usando compose.

Caso você pergunte qual diferença há com o que Ramda faz, eles usam sua própria implementação da função map.

Agora modifique view e over. Começando com view

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

Essa função constant pode parecer unnalmente complexo, mas tem a sua finalidade. As coisas podem ser enredadas muito quando você usa compose, essa estrutura é garantida que o valor que queremos permanecer intactos.

e over? É quase o mesmo, exceto que nesse caso, usamos a função .

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 agora devemos ter uma implementação quase adequada. Isto é o que temos sem contar as dependências (path e assocPath).

Você acredita que se eu disser que funciona? Você não deveria. Vamos fazer alguns testes. Vamos voltar com alice e vamos adicionar outro objeto, 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 porque tivemos tudo planejado de antes, já temos alguma lente disponível.

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

Suponha que desejamos manipular a propriedade dragon De cada um, tudo o que temos a fazer é combinar.

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"

Então, apenas manipulamos um objeto aninhado em vários níveis combinando lente. Nós resolvemos um problema combinando funções. Se você não acha ótimo, não sei o que mais dizer a eles.

estas coisas são difíceis de vender, porque exigem um estilo particular para poder aproveitar-os ao máximo. E para aqueles que usam JavaScript, provavelmente há uma biblioteca que resolve o mesmo problema, mas de uma maneira mais conveniente ou pelo menos se encaixa em seu estilo.

De qualquer forma, se eles ainda estão interessados em como eles Trabalhe estas lentes em uma revisão de contexto mais complexa Este repositório é um exemplo de “aplicativo do mundo real” (algo como um clone of Medium.com) usa o Hyperapp para lidar com a interface. O autor queria usar lente para lidar com o status do aplicativo.

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *