Je crois que j'ai appris certains / beaucoup / la plupart des concepts de base sous-jacents à la programmation fonctionnelle en JavaScript. Cependant, j'ai du mal à lire spécifiquement le code fonctionnel, même le code que j'ai écrit, et je me demande si quelqu'un peut me donner des conseils, des conseils, des meilleures pratiques, de la terminologie, etc. qui peuvent vous aider.
Prenez le code ci-dessous. J'ai écrit ce code. Il vise à attribuer un pourcentage de similitude entre deux objets, entre dire {a:1, b:2, c:3, d:3}
et {a:1, b:1, e:2, f:2, g:3, h:5}
. J'ai produit le code en réponse à cette question sur Stack Overflow . Parce que je n'étais pas sûr du type de pourcentage de similitude que l'affiche demandait, j'ai fourni quatre types différents:
- le pourcentage des clés du 1er objet que l'on peut trouver dans le 2ème,
- le pourcentage des valeurs du 1er objet que l'on peut trouver dans le 2e, y compris les doublons,
- le pourcentage des valeurs du 1er objet que l'on peut trouver dans le 2e, sans doublons autorisés, et
- le pourcentage de paires {key: value} dans le 1er objet que l'on peut trouver dans le 2ème objet.
J'ai commencé avec un code raisonnablement impératif, mais j'ai rapidement réalisé que c'était un problème bien adapté à la programmation fonctionnelle. En particulier, je me suis rendu compte que si je pouvais extraire une fonction ou trois pour chacune des quatre stratégies ci-dessus qui définissaient le type de fonctionnalité que je cherchais à comparer (par exemple, les touches ou les valeurs, etc.), alors je pourrais être capable de réduire (pardonner le jeu de mots) le reste du code en unités répétables. Vous savez, le garder au SEC. Je suis donc passé à la programmation fonctionnelle. Je suis assez fier du résultat, je pense que c'est assez élégant et je pense que je comprends assez bien ce que j'ai fait.
Cependant, même après avoir écrit le code moi-même et en avoir compris chaque partie pendant la construction, quand j'y repense maintenant, je continue d'être un peu déconcerté à la fois comment lire une demi-ligne particulière, ainsi que comment "grok" ce que fait une demi-ligne particulière de code. Je me retrouve à faire des flèches mentales pour connecter différentes parties qui se dégradent rapidement en un gâchis de spaghettis.
Alors, quelqu'un peut-il me dire comment "lire" certains des morceaux de code les plus compliqués d'une manière à la fois concise et qui contribue à ma compréhension de ce que je lis? Je suppose que les parties qui m'obtiennent le plus sont celles qui ont plusieurs grosses flèches d'affilée et / ou des pièces qui ont plusieurs parenthèses d'affilée. Encore une fois, dans leur cœur, je peux éventuellement comprendre la logique, mais (j'espère) il existe une meilleure façon de procéder rapidement et clairement et directement "en prenant" une ligne de programmation JavaScript fonctionnelle.
N'hésitez pas à utiliser n'importe quelle ligne de code ci-dessous, ou même d'autres exemples. Cependant, si vous voulez quelques suggestions initiales de ma part, en voici quelques-unes. Commencez par une solution assez simple. Près de la fin du code, il y a ce qui est passé comme paramètre à une fonction: obj => key => obj[key]
. Comment peut-on lire et comprendre cela? Un exemple plus est une fonction complète de près du début: const getXs = (obj, getX) => Object.keys(obj).map(key => getX(obj)(key));
. La dernière map
partie me touche particulièrement.
S'il vous plaît noter, à ce moment je ne cherche des références à Haskell ou notation symbolique abstraite ou les fondements de corroyage, etc. Ce que je suis à la recherche est phrases en anglais que je peux en silence la bouche tout en regardant une ligne de code. Si vous avez des références qui traitent précisément de cela, tant mieux, mais je ne recherche pas non plus de réponses qui disent que je devrais aller lire certains manuels de base. Je l'ai fait et j'obtiens (au moins une grande partie de) la logique. Notez également que je n'ai pas besoin de réponses exhaustives (bien que de telles tentatives soient les bienvenues): même des réponses courtes qui fournissent une manière élégante de lire une seule ligne particulière de code autrement gênant seraient appréciées.
Je suppose qu'une partie de cette question est: puis- je même lire le code fonctionnel de façon linéaire, vous savez, de gauche à droite et de haut en bas? Ou est-on à peu près obligé de créer une image mentale d'un câblage de type spaghetti sur la page de code qui n'est décidément pas linéaire? Et si l'on doit le faire, nous devons encore lire le code, alors comment prendre du texte linéaire et câbler les spaghettis?
Des conseils seraient appréciés.
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
la source
Je n'ai pas fait beaucoup de travail hautement fonctionnel en Javascript (ce que je dirais que c'est - la plupart des gens qui parlent de Javascript fonctionnel peuvent utiliser des cartes, des filtres et des réductions, mais votre code définit ses propres fonctions de niveau supérieur , ce qui est un peu plus avancé que cela), mais je l'ai fait à Haskell, et je pense qu'au moins une partie de l'expérience se traduit. Je vais vous donner quelques conseils sur les choses que j'ai apprises:
La spécification des types de fonctions est vraiment importante. Haskell ne vous oblige pas à spécifier le type d'une fonction, mais l'inclusion du type dans la définition facilite la lecture. Bien que Javascript ne prenne pas en charge la saisie explicite de la même manière, il n'y a aucune raison de ne pas inclure la définition de type dans un commentaire, par exemple:
Avec un peu de pratique pour travailler avec des définitions de type comme celles-ci, elles rendent le sens d'une fonction beaucoup plus clair.
La dénomination est importante, peut-être plus encore que dans la programmation procédurale. Un grand nombre de programmes fonctionnels sont écrits dans un style très laconique qui est lourd de convention (par exemple, la convention que «xs» est une liste / tableau et que «x» est un élément est très répandue), mais à moins que vous ne compreniez ce style je suggérerais facilement une dénomination plus détaillée. En regardant les noms spécifiques que vous avez utilisés, "getX" est plutôt opaque, et donc "getXs" n'aide pas vraiment non plus. J'appellerais "getXs" quelque chose comme "applyToProperties", et "getX" serait probablement "propertyMapper". "getPctSameXs" serait alors "percentPropertiesSameWith" ("avec").
Une autre chose importante est d' écrire du code idiomatique . Je remarque que vous utilisez une syntaxe
a => b => some-expression-involving-a-and-b
pour produire des fonctions au curry. Ceci est intéressant et pourrait être utile dans certaines situations, mais vous ne faites rien ici qui bénéficie des fonctions curry et il serait plus idiomatique Javascript d'utiliser à la place des fonctions traditionnelles à plusieurs arguments. Cela peut faciliter la vue d'ensemble de ce qui se passe. Vous utilisez égalementconst name = lambda-expression
pour définir des fonctions, où il serait plus idiomatique d'utiliser à lafunction name (args) { ... }
place. Je sais qu'ils sont sémantiquement légèrement différents, mais à moins que vous ne vous appuyiez sur ces différences, je suggère d'utiliser la variante la plus courante lorsque cela est possible.la source
obj => key => ...
peut être simplifiée(obj, key) => ...
car plus tardgetX(obj)(key)
peut également être simplifiéeget(obj, key)
. En revanche, une autre fonction curry,,(getX, filter = vals => vals) => (objA, objB) => ...
ne peut pas être facilement simplifiée, au moins dans le contexte du reste du code tel qu'il est écrit.