|>
) pour JavaScript (Ce document utilise %
comme jeton d'espace réservé pour la référence du sujet. Ce ne sera certainement pas le choix final ; voir la discussion sur le token bikeshedding pour plus de détails.)
Dans l'enquête State of JS 2020, la quatrième réponse la plus importante à la question « Selon vous, qu'est-ce qui manque actuellement dans JavaScript ? était un opérateur de canalisations . Pourquoi?
Lorsque nous effectuons des opérations consécutives (par exemple, des appels de fonction) sur une valeur en JavaScript, il existe actuellement deux styles fondamentaux :
C'est-à-dire three(two(one(value)))
contre value.one().two().three()
. Cependant, ces styles diffèrent beaucoup en termes de lisibilité, de fluidité et d’applicabilité.
Le premier style, nesting , est généralement applicable – il fonctionne pour n’importe quelle séquence d’opérations : appels de fonction, arithmétique, littéraux tableau/objet, await
et yield
, etc.
Cependant, l'imbrication est difficile à lire lorsqu'elle devient profonde : le flux d'exécution se déplace de droite à gauche , plutôt que la lecture de gauche à droite du code normal. S'il y a plusieurs arguments à certains niveaux, la lecture rebondit même : nos yeux doivent sauter à gauche pour trouver un nom de fonction, puis à droite pour trouver des arguments supplémentaires. De plus, modifier le code par la suite peut s'avérer fastidieux : nous devons trouver le bon endroit pour insérer de nouveaux arguments parmi de nombreuses parenthèses imbriquées .
Considérez ce code du monde réel de React.
console . log (
chalk . dim (
`$ ${ Object . keys ( envars )
. map ( envar =>
` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
} ` ,
'node' ,
args . join ( ' ' ) ) ) ;
Ce code du monde réel est constitué d' expressions profondément imbriquées . Afin de lire son flux de données, les yeux d'un humain doivent d'abord :
Recherchez les données initiales (l'expression la plus interne, envars
).
Et puis parcourez à plusieurs reprises de l'intérieur vers l'extérieur pour chaque transformation de données, chacune étant soit un opérateur de préfixe facilement manqué à gauche, soit un opérateur de suffixe à droite :
Object.keys()
(côté gauche),.map()
(côté droit),.join()
(côté droit),chalk.dim()
(côté gauche), puisconsole.log()
(côté gauche).En raison de l'imbrication profonde de nombreuses expressions (dont certaines utilisent des opérateurs de préfixe , d'autres utilisent des opérateurs de suffixe et d'autres utilisent des opérateurs de circonfixe ), nous devons vérifier les côtés gauche et droit pour trouver la tête de chaque expression .
Le deuxième style, chaînage de méthodes , n'est utilisable que si la valeur possède les fonctions désignées comme méthodes pour sa classe. Cela limite son applicabilité. Mais lorsqu'il s'applique, grâce à sa structure postfixée, il est généralement plus utilisable et plus facile à lire et à écrire. L'exécution du Code se déroule de gauche à droite . Les expressions profondément imbriquées sont démêlées . Tous les arguments d'un appel de fonction sont regroupés avec le nom de la fonction. Et modifier le code plus tard pour insérer ou supprimer davantage d'appels de méthode est trivial, puisqu'il nous suffirait de placer notre curseur au même endroit, puis de commencer à taper ou à supprimer une série de caractères contigus .
En effet, les avantages du chaînage de méthodes sont si attrayants que certaines bibliothèques populaires déforment leur structure de code spécifiquement pour permettre davantage de chaînage de méthodes . L'exemple le plus marquant est jQuery , qui reste toujours la bibliothèque JS la plus populaire au monde. La conception principale de jQuery est un seul über-objet contenant des dizaines de méthodes, qui renvoient toutes le même type d'objet afin que nous puissions continuer à enchaîner . Il y a même un nom pour ce style de programmation : interfaces fluides .
Malheureusement, malgré toute sa fluidité, le chaînage de méthodes ne peut à lui seul s'adapter aux autres syntaxes de JavaScript : appels de fonction, arithmétique, littéraux tableau/objet, await
et yield
, etc. De cette manière, le chaînage de méthodes reste limité dans son applicabilité .
L'opérateur de canal tente d'allier la commodité et la facilité du chaînage de méthodes avec la large applicabilité de l'imbrication d'expressions .
La structure générale de tous les opérateurs de canal est value |>
e1 |>
e2 |>
e3 , où e1 , e2 , e3 sont toutes des expressions qui prennent des valeurs consécutives comme paramètres. L'opérateur |>
fait ensuite un certain degré de magie pour « diriger » value
du côté gauche vers le côté droit.
Poursuivant ce code du monde réel profondément imbriqué de React :
console . log (
chalk . dim (
`$ ${ Object . keys ( envars )
. map ( envar =>
` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
} ` ,
'node' ,
args . join ( ' ' ) ) ) ;
…nous pouvons le démêler tel quel en utilisant un opérateur de tube et un jeton d'espace réservé ( %
) remplaçant la valeur de l'opération précédente :
Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
| > `$ ${ % } `
| > chalk . dim ( % , 'node' , args . join ( ' ' ) )
| > console . log ( % ) ;
Désormais, le lecteur humain peut retrouver rapidement les données initiales (ce qui était l'expression la plus intime, envars
), puis lire linéairement , de gauche à droite , chaque transformation sur les données.
On pourrait affirmer que l’utilisation de variables temporaires devrait être le seul moyen de démêler le code profondément imbriqué. Nommer explicitement la variable de chaque étape provoque quelque chose de similaire au chaînage de méthodes, avec des avantages similaires à la lecture et à l'écriture de code.
Par exemple, en utilisant notre précédent exemple réel modifié de React :
Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
| > `$ ${ % } `
| > chalk . dim ( % , 'node' , args . join ( ' ' ) )
| > console . log ( % ) ;
…une version utilisant des variables temporaires ressemblerait à ceci :
const envarString = Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' ) ;
const consoleText = `$ ${ envarString } ` ;
const coloredConsoleText = chalk . dim ( consoleText , 'node' , args . join ( ' ' ) ) ;
console . log ( coloredConsoleText ) ;
Mais il y a des raisons pour lesquelles nous rencontrons tout le temps des expressions profondément imbriquées dans le code de chacun dans le monde réel , plutôt que des lignes de variables temporaires. Et il y a des raisons pour lesquelles les interfaces fluides basées sur une chaîne de méthodes de jQuery, Mocha, etc. sont toujours populaires .
Il est souvent tout simplement trop fastidieux et verbeux d' écrire du code avec une longue séquence de variables temporaires à usage unique. Il est sans doute même fastidieux et visuellement bruyant de lire pour un humain.
Si la dénomination est l’une des tâches les plus difficiles en programmation, alors les programmeurs éviteront inévitablement de nommer des variables lorsqu’ils percevront que leur avantage est relativement faible.
On pourrait affirmer que l’utilisation d’une seule variable mutable avec un nom court réduirait la verbosité des variables temporaires, obtenant des résultats similaires à ceux de l’opérateur de canal.
Par exemple, notre précédent exemple réel modifié de React pourrait être réécrit comme ceci :
let _ ;
_ = Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' ) ;
_ = `$ ${ _ } ` ;
_ = chalk . dim ( _ , 'node' , args . join ( ' ' ) ) ;
_ = console . log ( _ ) ;
Mais un code comme celui-ci n’est pas courant dans le code du monde réel. L'une des raisons à cela est que les variables mutables peuvent changer de manière inattendue , provoquant des bogues silencieux difficiles à trouver. Par exemple, la variable peut être accidentellement référencée dans une fermeture. Ou bien il peut être réaffecté par erreur dans une expression.
// setup
function one ( ) { return 1 ; }
function double ( x ) { return x * 2 ; }
let _ ;
_ = one ( ) ; // _ is now 1.
_ = double ( _ ) ; // _ is now 2.
_ = Promise . resolve ( ) . then ( ( ) =>
// This does *not* print 2!
// It prints 1, because `_` is reassigned downstream.
console . log ( _ ) ) ;
// _ becomes 1 before the promise callback.
_ = one ( _ ) ;
Ce problème ne se produirait pas avec l’opérateur de canalisation. Le jeton de sujet ne peut pas être réaffecté et le code en dehors de chaque étape ne peut pas modifier sa liaison.
let _ ;
_ = one ( )
| > double ( % )
| > Promise . resolve ( ) . then ( ( ) =>
// This prints 2, as intended.
console . log ( % ) ) ;
_ = one ( ) ;
Pour cette raison, le code avec des variables mutables est également plus difficile à lire. Pour déterminer ce que la variable représente à un moment donné, vous devez rechercher dans toute la portée précédente les endroits où elle est réaffectée .
La référence thématique d’un pipeline, en revanche, a une portée lexicale limitée et sa liaison est immuable dans sa portée. Il ne peut pas être réaffecté accidentellement et peut être utilisé en toute sécurité dans les fermetures.
Bien que la valeur du sujet change également à chaque étape du pipeline, nous analysons uniquement l'étape précédente du pipeline pour lui donner un sens, ce qui conduit à un code plus facile à lire.
Un autre avantage de l'opérateur pipe par rapport aux séquences d'instructions d'affectation (qu'elles soient avec des variables temporaires mutables ou immuables) est qu'il s'agit d'expressions .
Les expressions pipe sont des expressions qui peuvent être directement renvoyées, affectées à une variable ou utilisées dans des contextes tels que les expressions JSX.
En revanche, l’utilisation de variables temporaires nécessite des séquences d’instructions.
Pipelines | Variables temporaires |
---|---|
const envVarFormat = vars =>
Object . keys ( vars )
. map ( var => ` ${ var } = ${ vars [ var ] } ` )
. join ( ' ' )
| > chalk . dim ( % , 'node' , args . join ( ' ' ) ) ; | const envVarFormat = ( vars ) => {
let _ = Object . keys ( vars ) ;
_ = _ . map ( var => ` ${ var } = ${ vars [ var ] } ` ) ;
_ = _ . join ( ' ' ) ;
return chalk . dim ( _ , 'node' , args . join ( ' ' ) ) ;
} |
// This example uses JSX.
return (
< ul >
{
values
| > Object . keys ( % )
| > [ ... Array . from ( new Set ( % ) ) ]
| > % . map ( envar => (
< li onClick = {
( ) => doStuff ( values )
} > { envar } < / li >
) )
}
< / ul >
) ; | // This example uses JSX.
let _ = values ;
_ = Object . keys ( _ ) ;
_ = [ ... Array . from ( new Set ( _ ) ) ] ;
_ = _ . map ( envar => (
< li onClick = {
( ) => doStuff ( values )
} > { envar } < / li >
) ) ;
return (
< ul > { _ } < / ul >
) ; |
Il y avait deux propositions concurrentes pour l'opérateur de tuyaux : les tuyaux Hack et les tuyaux F#. (Avant cela, il y avait une troisième proposition pour un « mélange intelligent » des deux premières propositions, mais elle a été retirée, car sa syntaxe est strictement un sur-ensemble de l'une des propositions.)
Les deux propositions de tubes diffèrent légèrement sur ce qu'est la « magie », lorsque nous épelons notre code lorsque nous utilisons |>
.
Les deux propositions réutilisent des concepts de langage existants : les tubes hack sont basés sur le concept d' expression , tandis que les tubes F# sont basés sur le concept de fonction unaire .
Les expressions de tuyauterie et les fonctions unaires de tuyauterie ont en conséquence des compromis faibles et presque symétriques.
Dans la syntaxe du tube du langage Hack , le côté droit du tube est une expression contenant un espace réservé spécial, qui est évalué avec l'espace réservé lié au résultat de l'évaluation de l'expression du côté gauche. Autrement dit, nous écrivons value |> one(%) |> two(%) |> three(%)
pour canaliser value
à travers les trois fonctions.
Avantages : Le côté droit peut être n'importe quelle expression , et l'espace réservé peut aller partout où n'importe quel identifiant de variable normal pourrait aller, nous pouvons donc diriger vers n'importe quel code que nous voulons sans aucune règle spéciale :
value |> foo(%)
pour les appels de fonction unaire,value |> foo(1, %)
pour les appels de fonction n-aire,value |> %.foo()
pour les appels de méthode,value |> % + 1
pour l'arithmétique,value |> [%, 0]
pour les littéraux de tableau,value |> {foo: %}
pour les littéraux d'objet,value |> `${%}`
pour les littéraux de modèle,value |> new Foo(%)
pour construire des objets,value |> await %
pour les promesses en attente,value |> (yield %)
pour produire des valeurs de générateur,value |> import(%)
pour appeler des mots-clés de type fonction, Inconvénient : le passage à travers des fonctions unaires est légèrement plus détaillé avec les tuyaux Hack qu'avec les tuyaux F#. Cela inclut les fonctions unaires créées par des bibliothèques de curry de fonctions comme Ramda, ainsi que les fonctions fléchées unaires qui effectuent une déstructuration complexe de leurs arguments : les tuyaux de piratage seraient légèrement plus verbeux avec un suffixe d'appel de fonction explicite (%)
.
(La déstructuration complexe de la valeur du sujet sera plus facile lorsque les expressions progresseront, car vous pourrez alors effectuer une affectation/déstructuration de variables à l'intérieur d'un corps de tuyau.)
Dans la syntaxe du tube du langage F# , le côté droit du tube est une expression qui doit être évaluée en une fonction unaire , qui est ensuite tacitement appelée avec la valeur du côté gauche comme seul argument . Autrement dit, nous écrivons value |> one |> two |> three
pour canaliser value
à travers les trois fonctions. left |> right
devient right(left)
. C'est ce qu'on appelle la programmation tacite ou le style sans points.
Par exemple, en utilisant notre précédent exemple réel modifié de React :
Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
| > `$ ${ % } `
| > chalk . dim ( % , 'node' , args . join ( ' ' ) )
| > console . log ( % ) ;
…une version utilisant des tubes F# au lieu de tubes Hack ressemblerait à ceci :
Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
| > x => `$ ${ x } `
| > x => chalk . dim ( x , 'node' , args . join ( ' ' ) )
| > console . log ;
Avantages : La restriction selon laquelle le membre de droite doit résoudre une fonction unaire nous permet d'écrire des tuyaux très concis lorsque l'opération que nous voulons effectuer est un appel de fonction unaire :
value |> foo
pour les appels de fonctions unaires. Cela inclut les fonctions unaires créées par des bibliothèques de currying de fonctions comme Ramda, ainsi que les fonctions fléchées unaires qui effectuent une déstructuration complexe de leurs arguments : les tubes F# seraient légèrement moins verbeux avec un appel de fonction implicite (no (%)
).
Inconvénient : la restriction signifie que toutes les opérations effectuées par une autre syntaxe doivent être rendues légèrement plus détaillées en enveloppant l'opération dans une fonction de flèche unaire :
value |> x=> x.foo()
pour les appels de méthode,value |> x=> x + 1
pour l'arithmétique,value |> x=> [x, 0]
pour les littéraux de tableau,value |> x=> ({foo: x})
pour les littéraux d'objet,value |> x=> `${x}`
pour les littéraux de modèle,value |> x=> new Foo(x)
pour construire des objets,value |> x=> import(x)
pour appeler des mots-clés de type fonction,Même l'appel de fonctions nommées nécessite un emballage lorsque nous devons passer plus d'un argument :
value |> x=> foo(1, x)
pour les appels de fonction n-aire. Inconvénient : les opérations await
et yield
sont limitées à leur fonction conteneur et ne peuvent donc pas être gérées par des fonctions unaires seules. Si l'on souhaite les intégrer dans une expression tube, await
et yield
doivent être traités comme des cas de syntaxe particuliers :
value |> await
pour les promesses en attente, etvalue |> yield
pour générer des valeurs de générateur. Les tubes Hack et les tubes F# imposent respectivement une petite taxe de syntaxe sur différentes expressions :
Les hack pipes taxent légèrement uniquement les appels de fonctions unaires , et
Les tubes F# taxent légèrement toutes les expressions à l'exception des appels de fonction unaire.
Dans les deux propositions, la taxe de syntaxe par expression taxée est faible ( les deux (%)
et x=>
ne sont que trois caractères ). Cependant, l'impôt est multiplié par la prévalence de ses expressions respectivement taxées. Il pourrait donc être judicieux d'imposer une taxe sur les expressions les moins courantes et d' optimiser en faveur des expressions les plus courantes .
Les appels de fonctions unaires sont en général moins courants que toutes les expressions à l'exception des fonctions unaires. En particulier, les appels de méthodes et les appels de fonctions n-aires seront toujours populaires ; en général, la fréquence des appels de fonctions unaires est égale ou dépassée dans ces deux cas seuls – sans parler d'autres syntaxes omniprésentes telles que les littéraux de tableau , les littéraux d'objet et les opérations arithmétiques . Cette fiche explicative contient plusieurs exemples concrets de cette différence de prévalence.
En outre, plusieurs autres nouvelles syntaxes proposées, telles que les appels d'extension , les expressions do et les littéraux d'enregistrement/tuple , deviendront probablement également omniprésentes à l' avenir . De même, les opérations arithmétiques deviendraient encore plus courantes si le TC39 normalisait la surcharge des opérateurs . Démêler les expressions de ces futures syntaxes serait plus fluide avec les tubes Hack qu'avec les tubes F#.
La taxe de syntaxe des tubes Hack sur les appels de fonction unaire (c'est-à-dire le (%)
pour invoquer la fonction unaire du côté droit) n'est pas un cas particulier : il s'agit simplement d'écrire explicitement du code ordinaire , comme nous le ferions normalement sans tube.
D'un autre côté, les tubes F# nous obligent à faire la distinction entre « le code qui se résout en une fonction unaire » et « toute autre expression » – et à ne pas oublier d'ajouter le wrapper de fonction flèche autour de ce dernier cas.
Par exemple, avec les tubes Hack, value |> someFunction + 1
est une syntaxe non valide et échouera prématurément . Il n’est pas nécessaire de reconnaître que someFunction + 1
ne sera pas évalué en fonction unaire. Mais avec les tubes F#, value |> someFunction + 1
est toujours une syntaxe valide – elle échouera simplement tard au moment de l'exécution , car someFunction + 1
n'est pas appelable.
Le groupe champion des pipes a présenté à deux reprises des pipes F# pour l'étape 2 au TC39. Il n’a pas réussi à passer à l’étape 2 à chaque fois. Les deux tubes F# (et l'application de fonctions partielles (PFA)) ont rencontré de fortes réticences de la part de plusieurs autres représentants du TC39 en raison de diverses préoccupations. Ceux-ci comprenaient :
await
.Cette réaction s’est produite à l’extérieur du groupe des champions du pipe. Voir HISTORY.md pour plus d’informations.
Le groupe Pipe Champion est convaincu que n'importe quel opérateur de pipe est meilleur que rien, afin de linéariser facilement des expressions profondément imbriquées sans recourir à des variables nommées. De nombreux membres du groupe champion pensent que les tubes Hack sont légèrement meilleurs que les tubes F#, et certains membres du groupe champion pensent que les tubes F# sont légèrement meilleurs que les tubes Hack. Mais tout le monde dans le groupe des champions s'accorde à dire que les tubes F# ont rencontré beaucoup trop de résistance pour pouvoir dépasser le TC39 dans un avenir proche.
Pour souligner, il est probable qu'une tentative de passer des tubes Hack aux tubes F# aura pour conséquence que TC39 n'acceptera jamais aucun tube. La syntaxe PFA est également confrontée à une bataille difficile dans TC39 (voir HISTORY.md). De nombreux membres du groupe des champions des pipes pensent que c'est malheureux, et ils sont prêts à se battre à nouveau plus tard pour un mixage divisé en tubes F# et une syntaxe PFA. Mais il existe un certain nombre de représentants (y compris les implémenteurs de moteurs de navigateur) en dehors du Pipe Champion Group qui sont généralement contre l'encouragement de la programmation tacite (et de la syntaxe PFA), indépendamment des tubes Hack.
(Un projet de spécification formel est disponible.)
La référence du sujet %
est un opérateur nul . Il agit comme un espace réservé pour une valeur de sujet , et il a une portée lexicale et est immuable .
%
n'est pas un choix final (Le jeton précis pour la référence du sujet n'est pas final . %
pourrait plutôt être ^
, ou de nombreux autres jetons. Nous prévoyons de modifier le jeton réel à utiliser avant de passer à l'étape 3. Cependant, %
semble être le moins problématique sur le plan syntaxique, et il ressemble également aux espaces réservés des chaînes de format printf et aux littéraux de fonction #(%)
de Clojure .)
L' opérateur pipe |>
est un opérateur infixe qui forme une expression pipe (également appelée pipeline ). Il évalue son côté gauche (la tête du tuyau ou l'entrée du tuyau ), lie de manière immuable la valeur résultante (la valeur du sujet ) à la référence du sujet , puis évalue son côté droit (le corps du tuyau ) avec cette liaison. La valeur résultante du côté droit devient la valeur finale de l'expression pipe entière (la sortie du tube ).
La priorité de l'opérateur de canalisation est la même que :
=>
;=
, +=
, etc.;yield
et yield *
; Il est plus strict que l'opérateur virgule ,
.
Il est plus souple que tous les autres opérateurs.
Par exemple, v => v |> % == null |> foo(%, 0)
se regrouperait en v => (v |> (% == null) |> foo(%, 0))
,
ce qui à son tour équivaut à v => foo(v == null, 0)
.
Un corps de canal doit utiliser sa valeur de sujet au moins une fois . Par exemple, value |> foo + 1
est une syntaxe non valide , car son corps ne contient pas de référence de sujet. Cette conception est due au fait que l'omission de la référence du sujet dans le corps d'une expression pipe est presque certainement une erreur accidentelle du programmeur.
De même, une référence de sujet doit être contenue dans un corps de canal. L'utilisation d'une référence de sujet en dehors d'un corps de canal est également une syntaxe non valide .
Pour éviter tout regroupement confus, il est incorrect d'utiliser d'autres opérateurs ayant une priorité similaire (c'est-à-dire la flèche =>
, l'opérateur conditionnel ternaire ?
:
, les opérateurs d'affectation et l'opérateur yield
) comme tête de tuyau ou corps . Lorsque vous utilisez |>
avec ces opérateurs, nous devons utiliser des parenthèses pour indiquer explicitement quel regroupement est correct. Par exemple, a |> b ? % : c |> %.d
est une syntaxe invalide ; il doit être corrigé en a |> (b ? % : c) |> %.d
ou a |> (b ? % : c |> %.d)
.
Enfin, les liaisons de sujets à l'intérieur d'un code compilé dynamiquement (par exemple, avec eval
ou new Function
) ne peuvent pas être utilisées en dehors de ce code. Par exemple, v |> eval('% + 1')
générera une erreur de syntaxe lorsque l'expression eval
est évaluée au moment de l'exécution.
Il n'y a pas d'autres règles spéciales .
Un résultat naturel de ces règles est que, si nous devons interposer un effet secondaire au milieu d'une chaîne d'expressions pipe, sans modifier les données transmises, nous pouvons alors utiliser une expression virgule , comme with value |> (sideEffect(), %)
. Comme d'habitude, l'expression virgule sera évaluée à son côté droit %
, en passant essentiellement par la valeur du sujet sans la modifier. Ceci est particulièrement utile pour un débogage rapide : value |> (console.log(%), %)
.
Les seuls changements apportés aux exemples originaux étaient l'indentation et la suppression des commentaires.
Depuis jquery/build/tasks/sourceMap.js :
// Status quo
var minLoc = Object . keys ( grunt . config ( "uglify.all.files" ) ) [ 0 ] ;
// With pipes
var minLoc = grunt . config ( 'uglify.all.files' ) | > Object . keys ( % ) [ 0 ] ;
Depuis node/deps/npm/lib/unpublish.js :
// Status quo
const json = await npmFetch . json ( npa ( pkgs [ 0 ] ) . escapedName , opts ) ;
// With pipes
const json = pkgs [ 0 ] | > npa ( % ) . escapedName | > await npmFetch . json ( % , opts ) ;
Depuis underscore.js :
// Status quo
return filter ( obj , negate ( cb ( predicate ) ) , context ) ;
// With pipes
return cb ( predicate ) | > _ . negate ( % ) | > _ . filter ( obj , % , context ) ;
De ramda.js.