8
votes

Pouvez-vous me donner un exemple d'utilisation de l'ascenseur Ramda?

Je lis la documentation ramda

const madd3 = R.lift((a, b, c) => a + b + c);

madd3([1,2,3], [1,2,3], [1]); //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]

Cela semble être une fonction vraiment utile. Je ne vois pas quel serait un cas d'utilisation pour cela.

Merci


1 commentaires

Hmm, je pensais que nous avions nettoyé cette documentation. Un exemple plus clair serait madd3 ([100, 200], [30, 40], [5, 6, 7]) // => [135, 136, 137, 145, 146, 147, 235, 236 , 237, 245, 246, 247] . Les réponses données sont excellentes, mais consultez également stackoverflow.com/q/36558598/1243641 .


4 Réponses :


4
votes

Cette fonction ne peut accepter que des nombres:

const results = add3Lifted([1, 10], [2], [3]);

Mais que se passerait-il si ces nombres étaient chacun contenus dans un foncteur? (c'est-à-dire une chose qui contient une valeur; un tableau dans l'exemple ci-dessous)

const results = [add3(1, 2, 3), add3(10, 2, 3)];

Ce n'est évidemment pas ce que nous voulons. Vous pouvez "soulever" la fonction pour qu'elle puisse "extraire" la valeur de chaque paramètre / foncteur:

add3Lifted([1, 10], [2], [3]);
//=> [6, 15]

Les tableaux peuvent évidemment contenir plus d'une valeur et être combinés avec un fonction qui sait extraire les valeurs de chaque foncteur, vous pouvez maintenant faire ceci:

[
  add3(1, 2, 3),    // 6
  add3(1, 2, 30),   // 33
  add3(1, 20, 3),   // 24
  add3(1, 20, 30),  // 51
  add3(10, 2, 3),   // 15
  add3(10, 2, 30),  // 42
  add3(10, 20, 3),  // 33
  add3(10, 20, 30)  // 60
]

Ce qui est essentiellement ce que vous auriez obtenu si vous aviez fait ceci: p>

add3Lifted([1, 10], [2, 20], [3, 30]);
//=> [6, 33, 24, 51, 15, 42, 33, 60]

Notez que chaque tableau n'a pas besoin d'être de la même longueur:

const add3Lifted = lift(add3);
add3Lifted([1], [2], [3]); //=> [6]

Donc, pour répondre à votre question: si vous avez l'intention d'exécuter une fonction avec différents ensembles de valeurs, la levée de cette fonction peut être une chose utile à considérer:

add3([1], [2], [3]); //=> "123"

équivaut à:

const add3 = (a, b, c) => a + b + c;
add3(1, 2, 3); //=> 6


0 commentaires

1
votes

Fondamentalement, il prend un produit cartésien et applique une fonction à chaque tableau.

const
    cartesian = (a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []),
    fn = ([a, b, c]) => a + b + c,
    result = [[1, 2, 3], [1, 2, 3], [1]]
        .reduce(cartesian)
        .map(fn);

console.log(result); // [3, 4, 5, 4, 5, 6, 5, 6, 7]


0 commentaires

2
votes

Ce qui n'a pas été mentionné dans les réponses actuelles, c'est que des fonctions telles que R.lift ne fonctionneront pas seulement avec des tableaux, mais aussi avec tout Apply 1 type de données.

Par exemple, nous pouvons réutiliser la même fonction produite par R. lift :

const Asynchronous = fn => ({
  run: fn,
  map: f    => Asynchronous(g => fn(a => g(f(a)))),
  ap: other => Asynchronous(fb => fn(f => other.run(a => fb(f(a)))))
})

const delay = (n, x) => Asynchronous(then => void(setTimeout(then, n, x)))

lifted(delay(2000, 4), delay(1000, 6), delay(500, 8)).run(console.log)

Avec des fonctions comme type Apply:

const Just = val => ({
  map: f    => Just(f(val)),
  ap: other => other.map(otherVal => val(otherVal)),
  getOr: _  => val
})

const Nothing = {
  map: f    => Nothing,
  ap: other => Nothing,
  getOr: x  => x
}

lifted(Just(4), Just(6), Just(8)).getOr(NaN) //=> 2

lifted(Just(4), Nothing, Just(8)).getOr(NaN) //=> NaN

Types facultatifs (distribution à .ap ):

lifted(a => a * a,
       b => b + 5,
       c => c * 3)(4) //=> 13

Types asynchrones (envoi vers .ap):

const lifted = lift((a, b, c) => a + b - c)


0 commentaires

5
votes

La programmation fonctionnelle est un sujet long et mathématique, en particulier la partie traitant des monades et théorie de la cathegorie en général . Mais il vaut la peine d'y jeter un œil, voici un introduction amusante avec des images .

En bref, lift est une fonction qui prendra une fonction n-arguments et produira une fonction qui prend n wrapped-values ​​ et produit une autre valeur wrapped-value résultante. Un ascenseur qui accepte une fonction à un argument est défini par la signature de type suivante

<script src="https://cdn.jsdelivr.net/npm/ramda@0.26.1/dist/ramda.min.js"></script>

Wait ... Wrapped-value?

Je présenterai brièvement Haskell, uniquement pour l'expliquer. Dans haskell, un exemple simple de valeur encapsulée est Maybe , Maybe peut être une valeur encapsulée ou rien, c'est aussi une valeur encapsulée. L'exemple suivant applique une fonction à un Maybe contenant une valeur et un Maybe vide.

const add2 = (a, b) => a + b;
const madd2 = R.lift(add2);

const res = madd2([1,2,3], [2,3,4]); 
//=> [3, 4, 5, 4, 5, 6, 5, 6, 7]

console.log(res);
// Equivalent to lift using ap
const result2 = R.ap(R.ap(
  [R.curry(add2)], [1, 2, 3]),
  [2, 3, 4]
  );
//=> [3, 4, 5, 4, 5, 6, 5, 6, 7]
console.log(result2);

La liste est également une valeur encapsulée, et nous pouvons lui appliquer des fonctions. Dans le second cas, liftA2 applique des fonctions à deux arguments à deux listes.

[a → b] → [a] → [b]
Apply f => f (a → b) → f a → f b

Cette valeur encapsulée est une application Functor , donc à partir de maintenant je l'appellerai Applicative.

Peut-être que vous commencez se désintéresser de ce point ... Mais quelqu'un avant nous s'est perdu sur ce sujet, enfin il a survécu et l'a publié comme réponse à cette question .

Regardons ce qu'il a vu ...

...

 Qu'avez-vous vu? (Xkcd comic) a >

Il a vu Fantasy Land

Dans fantasy-land, un objet implémente Apply a > spec quand il a une méthode ap définie (cet objet doit également implémenter Functor spécification en définissant une carte code> méthode).

  • Fantasy-land est un nom sophistiqué pour une spécification de programmation fonctionnelle javascript. Ramda le suit.
  • Apply est notre applicatif, un Functor qui implémente également une méthode ap .
  • Un Functor , est quelque chose qui a la méthode map .

Alors, attendez ... le Array en javascript a une carte ...

<script src="https://cdn.jsdelivr.net/npm/ramda@0.26.1/dist/ramda.min.js"></script>

Alors le Array est un Functor, et map applique une fonction à toutes ses valeurs, en renvoyant un autre Functor avec le même nombre de valeurs.

Mais que fait le ap faire?

ap applique une liste de fonctions à une liste de valeurs.

Envoie à la méthode ap du deuxième argument, s'il est présent. Également traite les fonctions curry comme des applicatifs.

Essayons de faire quelque chose avec.

const res = R.ap(
  [
    (a)=>(-1*a), 
    (a)=>((a>1?'greater than 1':'a low value'))
  ], 
  [1,2,3]); //=>  [ -1, -2, -3, "a low value", "greater than 1", "greater than 1" ]

console.log(res);
[1,2,3].map((a)=>a+1) \\=> [ 2, 3, 4 ]

La méthode ap prend un tableau (ou un autre applicatif) de fonctions et l'applique à un applicatif de valeurs pour produire un autre applicatif aplati.

La signature de la méthode explique cela

> liftA (+ 8) [1,2,3]
[9,10,11]
> liftA2 (*) [1..3] [1..3]
[1,2,3,2,4,6,3,6,9]

Enfin, que fait le lift?

Lift prend une fonction avec n arguments, et produit une autre fonction qui prend n Aplicatives et produit une Aplicative aplatie des résultats.

Dans ce cas, notre Applicative est le tableau.

> liftA (+ 8) (Just 8)
Just 16
> liftA (+ 8) Nothing
Nothing
 // name :: f is a wrp-value => function -> wrp-value -> wrp-value
 liftA :: Applicative f   => (a -> b) -> f a -> f b

Ces wrappers (Applicatifs, Functeurs, Monades) sont intéressants car ils peuvent être tout ce qui implémente ces méthodes. Dans haskell, ceci est utilisé pour encapsuler les opérations non sécurisées, telles que les entrées / sorties. Il peut également s'agir d'un wrapper d'erreur ou d'un arbre, voire de toute structure de données.


2 commentaires

En général, lift , ou plus précisément liftA2 équivaut à ap (map (x)) , et non à ap (ap (x) )) . Ceci est donc spécifique à Ramda et s'écarte de la norme.


@reify: Ramda met en œuvre liftN (et donc lift ) avec une map initiale, réduisant les appels ap sur le résultat.