Acredito que aprendi alguns / muitos / a maioria dos conceitos básicos subjacentes à programação funcional em JavaScript. No entanto, tenho problemas para ler especificamente o código funcional, mesmo o código que escrevi, e me pergunto se alguém pode me dar sugestões, dicas, práticas recomendadas, terminologia etc. que possam ajudar.
Pegue o código abaixo. Eu escrevi esse código. O objetivo é atribuir uma porcentagem de semelhança entre dois objetos, entre dizer {a:1, b:2, c:3, d:3}
e {a:1, b:1, e:2, f:2, g:3, h:5}
. Eu produzi o código em resposta a esta pergunta no Stack Overflow . Como não tinha certeza exatamente de que tipo de similaridade percentual o cartaz estava perguntando, forneço quatro tipos diferentes:
- a porcentagem de chaves no primeiro objeto que pode ser encontrada no segundo,
- a porcentagem dos valores no primeiro objeto que podem ser encontrados no segundo, incluindo duplicatas,
- a porcentagem dos valores no 1º objeto que podem ser encontrados no 2º, sem duplicatas permitidas, e
- a porcentagem de {key: value} emparelha no primeiro objeto que pode ser encontrado no segundo objeto.
Comecei com um código razoavelmente imperativo, mas rapidamente percebi que esse era um problema adequado para a programação funcional. Em particular, percebi que, se eu pudesse extrair uma função ou três para cada uma das quatro estratégias acima, que definiam o tipo de recurso que eu estava procurando comparar (por exemplo, as chaves, os valores etc.), então eu poderia estar capaz de reduzir (perdoar o jogo de palavras) o restante do código em unidades repetíveis. Você sabe, mantendo-o SECO. Então mudei para a programação funcional. Estou muito orgulhoso do resultado, acho que é razoavelmente elegante e acho que entendo o que fiz muito bem.
No entanto, mesmo tendo escrito o código e compreendendo cada parte dele durante a construção, quando agora olho para trás, continuo um pouco confuso com o modo de ler qualquer meia-linha em particular e com o modo de ler. "grok" o que qualquer meia-linha específica de código está realmente fazendo. Eu me pego fazendo flechas mentais para conectar diferentes partes que rapidamente se degradam em uma bagunça de espaguete.
Então, alguém pode me dizer como "ler" alguns dos pedaços de código mais complicados de uma maneira que seja concisa e que contribua para minha compreensão do que estou lendo? Eu acho que as partes que mais me atraem são aquelas que têm várias flechas grossas seguidas e / ou partes que têm vários parênteses seguidos. Mais uma vez, no âmago, posso finalmente descobrir a lógica, mas (espero), há uma maneira melhor de agir rápida e clara e diretamente "incorporando" uma linha de programação JavaScript funcional.
Sinta-se livre para usar qualquer linha de código abaixo ou até outros exemplos. No entanto, se você quiser algumas sugestões iniciais, aqui estão algumas. Comece com um razoavelmente simples. Perto do final do código, não há esse que é passado como um parâmetro para uma função: obj => key => obj[key]
. Como alguém lê e entende isso? Um exemplo é mais uma função completa de perto do início: const getXs = (obj, getX) => Object.keys(obj).map(key => getX(obj)(key));
. A última map
parte me deixa em particular.
Por favor nota, neste momento eu estou não procurando referências a Haskell ou notação abstrata simbólica ou os fundamentos do currying, etc. O que eu estou procurando é frases em inglês que eu possa silenciosamente boca enquanto olha para uma linha de código. Se você tem referências que abordam exatamente exatamente isso, ótimo, mas também não estou procurando respostas que digam que eu deveria ler alguns livros básicos. Eu fiz isso e recebo (pelo menos uma quantidade significativa) da lógica. Observe também que não preciso de respostas exaustivas (embora essas tentativas sejam bem-vindas): Mesmo respostas curtas que fornecem uma maneira elegante de ler uma única linha específica de código problemático seriam apreciadas.
Suponho que uma parte dessa pergunta seja: Posso ler o código funcional linearmente, da esquerda para a direita e de cima para baixo? Ou alguém é forçado a criar uma imagem mental da fiação semelhante a espaguete na página de código que decididamente não é linear? E se é preciso fazer isso, ainda precisamos ler o código, então como pegar texto linear e conectar o espaguete?
Qualquer dica seria apreciada.
const obj1 = { a:1, b:2, c:3, d:3 };
const obj2 = { a:1, b:1, e:2, f:2, g:3, h:5 };
// x or X is key or value or key/value pair
const getXs = (obj, getX) =>
Object.keys(obj).map(key => getX(obj)(key));
const getPctSameXs = (getX, filter = vals => vals) =>
(objA, objB) =>
filter(getXs(objB, getX))
.reduce(
(numSame, x) =>
getXs(objA, getX).indexOf(x) > -1 ? numSame + 1 : numSame,
0
) / Object.keys(objA).length * 100;
const pctSameKeys = getPctSameXs(obj => key => key);
const pctSameValsDups = getPctSameXs(obj => key => obj[key]);
const pctSameValsNoDups = getPctSameXs(obj => key => obj[key], vals => [...new Set(vals)]);
const pctSameProps = getPctSameXs(obj => key => JSON.stringify( {[key]: obj[key]} ));
console.log('obj1:', JSON.stringify(obj1));
console.log('obj2:', JSON.stringify(obj2));
console.log('% same keys: ', pctSameKeys (obj1, obj2));
console.log('% same values, incl duplicates:', pctSameValsDups (obj1, obj2));
console.log('% same values, no duplicates: ', pctSameValsNoDups(obj1, obj2));
console.log('% same properties (k/v pairs): ', pctSameProps (obj1, obj2));
// output:
// obj1: {"a":1,"b":2,"c":3,"d":3}
// obj2: {"a":1,"b":1,"e":2,"f":2,"g":3,"h":5}
// % same keys: 50
// % same values, incl duplicates: 125
// % same values, no duplicates: 75
// % same properties (k/v pairs): 25
fonte
Eu não fiz muito trabalho altamente funcional em Javascript (o que eu diria que é isso - a maioria das pessoas falando sobre Javascript funcional pode estar usando mapas, filtros e reduções, mas seu código define suas próprias funções de nível superior , que é um pouco mais avançado que isso), mas já fiz isso em Haskell e acho que pelo menos parte da experiência se traduz. Vou dar algumas dicas para as coisas que aprendi:
Especificar os tipos de funções é realmente importante. Haskell não exige que você especifique qual é o tipo de uma função, mas incluir o tipo na definição facilita a leitura. Embora o Javascript não suporte a digitação explícita da mesma maneira, não há motivo para não incluir a definição de tipo em um comentário, por exemplo:
Com um pouco de prática no trabalho com definições de tipo como essa, elas tornam o significado de uma função muito mais claro.
A nomeação é importante, talvez até mais do que na programação processual. Muitos programas funcionais são escritos em um estilo muito conciso que é pesado em convenções (por exemplo, a convenção de que 'xs' é uma lista / matriz e que 'x' é um item é muito difundida), mas a menos que você entenda esse estilo facilmente eu sugeriria nomes mais detalhados. Observando nomes específicos que você usou, "getX" é meio opaco e, portanto, "getXs" também não ajuda muito. Eu chamaria "getXs" algo como "applyToProperties" e "getX" provavelmente seria "propertyMapper". "getPctSameXs" seria "percentPropertiesSameWith" ("with")
Outra coisa importante é escrever código idiomático . Percebo que você está usando uma sintaxe
a => b => some-expression-involving-a-and-b
para produzir funções com caril. Isso é interessante e pode ser útil em algumas situações, mas você não está fazendo nada aqui que se beneficie de funções com curry e seria mais Javascript idiomático usar funções tradicionais de vários argumentos. Isso pode facilitar a visualização rápida do que está acontecendo. Você também está usandoconst name = lambda-expression
para definir funções, onde seria mais idiomático usarfunction name (args) { ... }
. Eu sei que eles são semanticamente ligeiramente diferentes, mas, a menos que você esteja contando com essas diferenças, sugiro usar a variante mais comum quando possível.fonte
obj => key => ...
pode ser simplificada,(obj, key) => ...
porque mais tardegetX(obj)(key)
também pode ser simplificadaget(obj, key)
. Por outro lado, outra função com curry,,(getX, filter = vals => vals) => (objA, objB) => ...
não pode ser facilmente simplificada, pelo menos no contexto do restante do código, conforme escrito.