2017-05-17 1 views
3

J'essaie d'utiliser une approche fonctionnelle pour résoudre un problème particulier dans le cadre d'un exercice d'apprentissage de Ramda.js.Comment utiliser correctement Ramda/JS pour composer des fonctions

J'ai donc ce test:

it.only("map short name to long name POINTFREE",() => { 
    let options = [ { long: "perky", short: "p" }, { long: "turky", short: "t" } ]; 

    let lookupByShortName = R.find(R.propEq("short", "t")); 
    let result = lookupByShortName(options); 
    expect(result).to.have.property("long", "turky"); 
}); 

"options" est utilisé comme une séquence de recherche. J'ai besoin de convertir une série de chaînes spécifiées comme un seul char, d'être le plus long nom équivalent en se référant à la séquence d'options. Donc le caractère "t" devrait être converti en "turky" comme défini dans les options.

Cependant, ce n'est pas tout à fait structuré de la façon dont j'ai besoin d'être utile. La fonction 'lookupByShortName' n'est pas générale, elle est codée en dur avec la valeur "t". Ce que je veux c'est omettre le paramètre "t", de sorte que lorsque vous appelez lookupByShortName, car il devrait être curry (par R.find), il devrait retourner une fonction qui nécessite l'argument manquant. Donc, si je fais cela, le test échoue:

let lookupByShortName = R.find(R.propEq("short")); 

donc ici, lookupByShortName doit devenir une fonction qui nécessite un seul paramètre manquant, donc théoriquement, je pense que je devrais être en mesure d'invoquer cette fonction comme suit:

lookupByShortName("t") 

ou plus précisément (le "t" est ajouté à la fin):

let lookupByShortName = R.find(R.propEq("short"))("t"); 

... mais je ne me trompe, parce que cela ne fonctionne pas, le test échoue:

1) Map short arg name to long option name map short name to long name POINTFREE: TypeError: lookupByShortName is not a function at Context.it.only (test/validator.spec.js:744:20)

Je pensais d'une autre solution (qui ne fonctionne pas, mais je ne comprends pas pourquoi):

Depuis « t » est le 2ème paramètre qui est passé à R.propEq, utilisez la R .__ espace réservé, puis passer en « t » à la fin:

let lookupByShortName = R.find(R.propEq("short", R.__))("t"); 

Je travaille à travers une série d'articles sur un blog, et bien que je crois comprendre mieux, je ne suis toujours pas encore là.

Pouvez-vous jeter la lumière sur l'endroit où je me trompe, merci.

Répondre

3

La première question est pourquoi votre code ne fonctionne pas.

La manière la plus simple d'expliquer cela est avec les signatures de fonction.

Nous commençons par propEq:

propEq :: String -> a -> Object -> Boolean 

Voici comment il serait dans une langue comme Hakell. propEq est une fonction qui prend une chaîne et retourne une fonction qui prend quelque chose d'un type arbitraire et retourne une fonction qui prend un objet et retourne un booléen. Vous pouvez écrire plus explicitement comme

propEq :: String -> (a -> (Object -> Boolean)) 

Vous appelez cela avec la syntaxe quelque chose comme:

propEq('short')('t')({ long: "perky", short: "p" }); //=> false 

a une idée Ramda légèrement différente, à savoir que vous ne devez pas passer une à la temps.Donc, il y a plusieurs façons tout aussi valables pour appeler la fonction de Ramda:

propEq :: String -> (a -> (Object -> Boolean)) 
      String -> ((a, Object) -> Boolean) 
      (String, a) -> (Object -> Boolean) 
      (String, a, Object) -> Boolean 

qui, respectivement, signifierait l'appeler comme ceci:

propEq('short')('t')({ long: "perky", short: "p" }); //=> false 
propEq('short')('t', { long: "perky", short: "p" }); //=> false 
propEq('short', 't')({ long: "perky", short: "p" }); //=> false 
propEq('short', 't', { long: "perky", short: "p" }); //=> false 

Ensuite, nous avons find, qui ressemble à ceci:

find :: (a -> Boolean) -> [a] -> a 

qui, pour des raisons similaires, signifie l'une de celles-ci dans Ramda:

find :: (a -> Boolean) -> ([a] -> a) 
    :: ((a -> Boolean), [a]) -> a 

Lorsque vous appelez

find(propEq('short')) 

vous essayez de passer a -> Object -> Boolean comme premier argument à find, qui veut que son premier argument a -> Boolean. Bien que Javascript ne soit pas fortement typé, et Ramda n'essaie pas d'offrir beaucoup d'aide avec une frappe forte, vous avez une incompatibilité de type. Vous êtes en fait déjà coulé, bien que Ramda accepte votre fonction comme si cela fonctionnerait, et retourne une fonction du type [a] -> a. Mais cette fonction ne fonctionnera pas correctement, car ce que find fait est de passer chaque a dans [a] dans notre propEq('short') jusqu'à ce que l'un d'entre eux renvoie true. Cela n'arrivera jamais car la signature de propEq('short') est a -> Object -> Boolean, donc quand nous passons un a, nous n'avons pas de booléen, mais une fonction de Object à Boolean.

Ce type de discordance est la raison pour laquelle votre approche actuelle ne fonctionne pas.


La deuxième question est de savoir comment le faire fonctionner.

L'approche la plus simple est d'utiliser quelque chose comme ceci:

let lookupByShortName = (abbrv, options) => R.find(R.propEq("short", abbrv), options); 
lookupByShortName('t', options); //=> {"long": "turky", "short": "t"} 

C'est un code propre, clair. Je le laisserais probablement comme ça. Mais si vous voulez vraiment qu'il soit sans point, Ramda offre le useWith pour des situations comme celle-ci. Vous pouvez l'utiliser comme ceci:

let lookupByShortName = R.useWith(R.find, [R.propEq('short'), R.identity]); 

Cela peut être vu comme une fonction (curry) de deux paramètres. Le premier paramètre est passé à propEq('short'), renvoyant une nouvelle fonction de type (a -> Boolean) et le deuxième paramètre est passé à identity, ce qui ne fait aucune transformation, en passant simplement les valeurs intactes. Ensuite, ces deux résultats sont passés dans find

useWith et les converge similaires sont très spécifiques à Ramda. Si vous n'avez pas besoin de version sans point (par exemple, en tant qu'exercice d'apprentissage), la première version est probablement préférable.

+0

Un grand merci Scott pour votre réponse, j'ai besoin de temps pour examiner et bien comprendre, avant de commenter correctement. Bravo pour l'instant ... – Zephilim