Julia: Proposition : Abandonner puis supprimer la tuyauterie des fonctions

Créé le 30 janv. 2017  ·  37Commentaires  ·  Source: JuliaLang/julia

Proposition

Déprécier l'utilisation actuelle de |> en tant que pipe de fonction. Autrement dit, la syntaxe x |> f serait obsolète au profit de la syntaxe d'appel normale f(x) . Après la période d'obsolescence, Base.:(|>) serait indéfini.

Ce changement a été initialement suggéré par tkelman dans https://github.com/JuliaLang/julia/issues/16985#issuecomment -227015399.

Il y a eu beaucoup de débats controversés sur diverses syntaxes pour la tuyauterie de fonctions (en particulier, voir # 5571), avec des arguments pour imiter une variété de langages. Cette discussion a été eue _ad nauseum_ et je ne souhaite pas la ressasser. Ce n'est PAS le but de cette proposition.

Raisonnement

Un certain nombre de packages bien pensés et bien entretenus ont implémenté des macros qui fournissent une syntaxe de canalisation pratique pour une variété de cas d'utilisation, à la fois généraux et spécifiques. Les exemples incluent Lazy.jl , FunctionalData.jl , Pipe.jl et ChainMap.jl , entre autres.

StefanKarpinski et andyferris nous ont donné une composition de fonction arbitraire dans #17155, qui peut servir un objectif similaire dans de nombreuses situations.

Comme tkelman l'a également soutenu dans # 5571, le pipeline de fonctions dans Base est à l'envers de la syntaxe d'appel familière; avoir les deux dans le langage de base, c'est essentiellement approuver l'utilisation de 2 syntaxes disparates pour atteindre le même objectif. Bien qu'il existe souvent plusieurs façons d'écrire la même chose en utilisant des solutions dans Base, généralement les solutions adhèrent au moins à un modèle mental similaire. Dans ce cas, les syntaxes emploient des modèles mentaux littéralement opposés.

Les pipelines de fonctions violent le principe de moindre surprise en appliquant l'action après l'objet. Autrement dit, si vous lisez sum(x) , vous savez immédiatement quand vous voyez sum() que vous allez additionner les valeurs de l'argument. Lorsque vous voyez x |> sum , vous voyez x , puis tout d'un coup vous additionnez ses valeurs. Peu ou pas d'autres solutions de base placent l'action à la fin, ce qui fait passer l'intrus.

La tuyauterie a en effet un précédent dans d'autres langages, par exemple %>% de Hadley Wickham en R (qui ne fait pas partie de la base R), et parfois ce style/flux a du sens. Cependant, dans l'intérêt de la cohérence au sein de Base Julia, je propose que nous reportions la responsabilité de fournir la syntaxe de canalisation aux packages, qui peuvent redéfinir |> ou fournir des macros pratiques comme bon leur semble.

Éléments d'action

Si cette proposition est acceptée, les mesures à prendre seraient les suivantes :

  • [ ] Supprimer les utilisations de la syntaxe dans Base, le cas échéant
  • [ ] Fournir une dépréciation formelle pour Base.:(|>) en 0.6 ou 1.0
  • [ ] Supprimer dans une version ultérieure
deprecation design julep

Commentaire le plus utile

Si nous déprécions cela, devrions-nous également déprécier * pour la concaténation de chaînes ? Cela a des problèmes similaires à ceux de la redondance avec string(a, b) , et viole le principe de moindre surprise étant donné que a et b ne sont pas des nombres.

Plus généralement, nous devrions probablement déprécier toute notation infixe, car il est déroutant d'avoir plusieurs conventions d'appel comme *(a, b) vs a * b - nous pouvons réduire nos 3 syntaxes disparates actuelles à une seule et obtenir une cohérence totale. Pour éviter la laideur, nous pourrions envisager de déplacer l'appel de fonction à l'intérieur des parenthèses, et peut-être de supprimer également les virgules redondantes.

Tous les 37 commentaires

La tuyauterie de fonction fournit une syntaxe postfixée pour l'appel de fonction, ce qui est pratique au niveau du REPL pour la génération de données interactives et une visualisation/récapitulation supplémentaire.

Un cas d'utilisation que j'ai vu beaucoup de gens taper est

julia> somecomplicatedthingproducingarray
...

<ARROW UP>

julia> somecomplicatedthingproducingarray |> summarize

où la fonction summarize est quelque chose comme un graphique ou un histogramme

@jiahao Je ne dis pas que ce n'est pas utile, mais plutôt que nous devrions être cohérents au sein de Base et laisser les packages fournir des choses comme celle-ci.

il y a aussi ans pour l'utilisation de remplacement

Dans cette proposition, |> serait-il toujours analysé comme un opérateur infixe ?

@ajkeller34 : certainement, les packages seraient libres d'en faire ce qu'ils veulent (bien qu'ils devraient bien jouer les uns avec les autres en termes de piratage de type et de coexistence), sans autant de contrainte d'être sémantiquement compatibles avec l'ancien définition de base.

Supprimer les utilisations de la syntaxe dans Base, le cas échéant

Voici une tentative maintenant très obsolète que j'ai faite pour le faire : https://github.com/tkelman/julia/commit/212727cdc4aaa3221763580f15d42cfe198bcc1c
À l'époque, la plupart des utilisations dans la base étaient assez triviales. Quelques-unes des utilisations des tests de "diriger cette chose vers cette fonction anonyme" sont peut-être plus agréables avec la canalisation, mais comme la plupart d'entre elles réutilisaient la même fonction anonyme plusieurs fois, cela vaudrait probablement la peine de lui donner un nom et de l'appeler comme un fonctionnement normal à ce moment-là.

Au cas où quelqu'un serait curieux, j'ai maintenant ChainRecursive.jl . Je mettrai une annonce sur le discours concernant la désintégration de ChainMap.jl et de ses différents enfants une fois qu'il sera terminé.

Permettez-moi d'offrir une certaine résistance ici puisque j'ai un intérêt direct et un goût particulier pour ce que |> rend possible.

Je suis d'accord avec @jiahao que |> est très utile lorsque vous voulez essayer rapidement des choses dans le REPL. De plus, je le trouve également utile lorsque votre argument est trop gros ou mérite un certain équilibre (oui, je l'ai dit) . Dans le cas de l'exemple lié, il est en fait préférable que l'argument soit plus important que la fonction appelée. sum(x) est un exemple trop simple et devrait en effet être écrit comme sum(x) ). Dans Escher.jl, toutes les fonctions qui ajoutent des propriétés aux éléments ont une méthode curry. Cela concorde si bien avec |> (c'était prévu, ça marche aussi très bien avec map ) et c'est une joie de pouvoir essayer des choses à la fin de la ligne et voir la mise à jour de l'interface utilisateur immédiatement. Je n'ai pas besoin de trouver mon chemin jusqu'au début de l'expression et de faire le tour. Pour une utilisation avec Escher au moins, l'alternative suggérée est d'assigner de grandes expressions à des variables de noms composés comme padded_box_contents_aligned_right_tomato_background (ou pire box34 ) puis d'appeler une fonction dessus. Contrairement à la belle lecture <big UI expression> |> aligncontents(right) |> pad(1em) |> fillcolor("tomato")

Je sais qu'après cela, je peux définir |> à l'intérieur d'Escher et je le ferai probablement, mais cela tuera mon cerveau de voir WARNING: using Escher.|> in module YourPackage conflicts with an existing identifier. les packages donneront presque certainement des significations différentes à cela, ce qui pour moi est très alarmant!

StefanKarpinski et andyferris nous ont donné une composition de fonction arbitraire dans #17155, qui peut servir un objectif similaire dans de nombreuses situations.

L'alternative à box |> fill("orange") |> pad(2em) serait (fill("orange") ∘ pad(2em))(box) par opposition à box |> fill("orange") ∘ pad(2em) ? Ces deux semblent orthogonaux.

L'utilisation par Escher des fermetures en tant qu'objets me semble définir un DSL juste pour utiliser cette syntaxe (qui a de sérieuses limitations pour tout ce qui n'est pas à entrée unique, à sortie unique), où il serait probablement mieux servi , et plus généralisable, s'il utilisait l'une des multiples macros de chaînage disponibles.

Supprimer la définition de Base de ceci permettrait aux personnes qui aiment cette syntaxe de faire des choses plus intéressantes avec.

@shashi Je comprends vos arguments, mais vous seriez en mesure d'obtenir le même comportement en utilisant l'un des packages que j'ai cités dans le problème, n'est-ce pas? Par exemple, dans votre exemple Escher, vous pouvez utiliser FunctionalData pour faire <strong i="6">@p</strong> vbox(<really big thing>) | pad(2em) ou Lazy pour faire @> vbox(...) pad(2em) .

Supprimer la définition de Base de ceci permettrait aux personnes qui aiment cette syntaxe de faire des choses plus intéressantes avec.

Sauf qu'il ne sera pas utilisable, car le seul moyen sûr de l'utiliser serait alors Escher.|>(...) ou Lazy.|>(...) .

Hypothétiquement, comment utiliser |> comme opérateur infixe si vous utilisez deux packages différents qui le définissent et l'exportent, en supposant qu'il n'est pas défini dans Base ?

@kmsquire Cela dépend du cas d'utilisation. |> serait toujours analysé comme un opérateur infixe comme il l'est maintenant, il n'aurait tout simplement pas de valeur dans Base. Si vous l'utilisez dans une macro, peu importe la manière dont un package particulier le définit, car il devient simplement le premier argument d'une expression d'appel.

Prenons par exemple <| , qui est analysé comme un opérateur infixe mais qui n'a pas de valeur. Même si ce n'est pas défini, nous avons encore

julia> dump(:(a <| b))
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol <|
    2: Symbol a
    3: Symbol b
  typ: Any

Les packages peuvent définir et exporter des méthodes pour Base.:(<|) qui signifient différentes choses, tout comme on peut le faire avec + .

Mais les packages qui fournissent de belles fonctions de tuyauterie le font dans les macros, je suppose précisément pour cette raison.

FWIW, aucun package de chaînage n'aurait besoin d'utiliser |> pendant l'évaluation car pendant le chaînage, tout est compressé en une seule expression. J'imagine que si les packages définissent |> , ce sera précisément la définition de base. Bien qu'ils devraient probablement utiliser une macro de chaînage à la place. Voir DataFramesMeta pour un bon exemple de comment construire une interface qui fonctionne bien avec le chaînage.

Si nous déprécions cela, devrions-nous également déprécier * pour la concaténation de chaînes ? Cela a des problèmes similaires à ceux de la redondance avec string(a, b) , et viole le principe de moindre surprise étant donné que a et b ne sont pas des nombres.

Plus généralement, nous devrions probablement déprécier toute notation infixe, car il est déroutant d'avoir plusieurs conventions d'appel comme *(a, b) vs a * b - nous pouvons réduire nos 3 syntaxes disparates actuelles à une seule et obtenir une cohérence totale. Pour éviter la laideur, nous pourrions envisager de déplacer l'appel de fonction à l'intérieur des parenthèses, et peut-être de supprimer également les virgules redondantes.

|> serait toujours analysé comme un opérateur infixe comme il l'est maintenant, il n'aurait tout simplement pas de valeur dans Base. Si vous l'utilisez dans une macro, peu importe comment un package particulier le définit

Je ne sais toujours pas pourquoi nous devons le supprimer de Base.

@bramtayl fait un bon point :

J'imagine que si les packages définissent |> ce sera précisément la définition de base.

Et toujours la seule façon d'utiliser plus d'un package qui définit ceci est de ne pas l'utiliser infix.

Je ne vois pas pourquoi la suppression de la définition dans Base est nécessaire pour que |> soit utilisé dans les macros.

Ce n'est pas le cas. Ce que je veux dire, c'est que |> peut être utilisé dans des macros, quelle que soit la situation dans Base. Il en va de même pour tout opérateur qui analyse de manière appropriée. Le but de la proposition est de rendre Base auto-cohérent en termes d'appels de fonction, puis le comportement de canalisation peut être obtenu via des packages. Que les packages utilisent |> en particulier n'a pas d'importance ; ils pourraient tout aussi bien utiliser <| ou littéralement n'importe quel autre opérateur d'infixe.

@ararslan d' accord, ce n'était pas ce que je voulais demander, j'ai mis à jour mon commentaire juste après, désolé.

Quoi qu'il en soit, je ne comprends pas tout à fait le sentiment "Base autonome en termes d'appels de fonction". Il semble que cela ne fera que rendre plus difficile l'utilisation |> dans un contexte non macro. Personnellement, je crois que |> est une chose intéressante à apprendre pour un débutant, même si c'est surprenant. Cela permet au moins d'économiser des efforts au REPL. C'est assez amusant de se rendre compte plus tard que |> est une fonction comme n'importe quelle autre fonction infixe et renforce la leçon que les fonctions ne sont que des valeurs.

Peut-être juste quelque chose de formel : Veuillez discuter/décider des obsolescences et/ou des changements de syntaxe au début d'un cycle de publication, pas à la fin. Actuellement, tous les principaux développeurs et responsables de paquets consacrent du temps et de l'énergie à la finition de la version 0.6 et ils n'ont peut-être pas le temps de penser à une autre (bonne) idée.

"Je ne dis pas que ce n'est pas utile, mais plutôt qu'il faut être cohérent"

Parfois, l'utilité bat la cohérence ? Je n'étais pas au courant de l'incohérence, mais j'ai trouvé la syntaxe |> utile. S'il est supprimé, je n'aurai pas l'impression d'avoir gagné quoi que ce soit de tangible.

Une explication de mon vote négatif, si vous me le permettez :

Une grande partie de ce qui se trouve actuellement dans Base pourrait se produire dans des packages à la place. Devrions-nous déplacer les dictionnaires vers un package ? Peut-être lister des opérations comme trier et mélanger? Opérations de recouvrement, etc. ? Je suis sûr qu'il y a eu des discussions longues et détaillées sur ce qui devrait et ne devrait pas être inclus dans Base, mais je suppose qu'il y a trois raisons pour lesquelles certaines fonctionnalités pourraient être incluses dans base :
1) Cette fonctionnalité est nécessaire pour activer d'autres fonctionnalités dans la base.
2) Cette fonctionnalité est une partie essentielle du langage, de nombreux programmeurs et packages Julia l'utiliseront, et il est donc souhaitable d'avoir une seule implémentation/syntaxe sur laquelle tout le monde s'accorde, plutôt que la fragmentation de beaucoup de personnes roulant la leur.
3) L'inclusion de cette fonctionnalité dans la base rend Julia "brute" plus agréable à utiliser ou la rend plus complète, ce qui aide à l'évangélisation et à l'adoption de la langue.

Quelque chose comme sum probablement les 3 points, et je dirais que la tuyauterie de fonction atteint les deuxième et troisième points :

Dans la proposition initiale (bien écrite) et dans la discussion de ce fil, un thème commun est l'existence de plusieurs packages fournissant des fonctionnalités de type pipe via des macros : Lazy.jl, Pipe.jl, ChainMap.jl, etc. L'existence de plusieurs packages suggère fortement que de nombreuses personnes dans la communauté trouvent que la tuyauterie est une fonctionnalité utile et souhaitable, et la présence de ces packages dans ce fil de discussion suggère que de nombreuses personnes ici comprennent et soutiennent l'utilisation de la tuyauterie.

Étant donné que la tuyauterie est une caractéristique courante et populaire dans la communauté Julia et dans d'autres langues, même dans cette discussion, les gens semblent convenir qu'elle a de nombreuses utilisations, en particulier au REPL (où Julia brille), et il y a déjà une fragmentation dans l'écosystème Julia. ..ma lecture n'est pas qu'il devrait être supprimé de Base, mais plutôt que la syntaxe de tuyauterie disponible dans Base devrait être améliorée afin qu'il y ait moins besoin de fragmentation. Différents packages offrant différentes manières de tracer, par exemple, semblent corrects; différents packages populaires offrant différentes manières d'appliquer les fonctions semblent assez effrayants.

Je soutiens en outre que la suppression de la tuyauterie de Base mais en laissant l'opérateur d'infixe est plutôt surprenant : dans Julia, vous ne pouvez pas définir vos propres opérateurs d'infixe, mais il y a un opérateur d'infixe inutilisé |> que vous pouvez définir comme vous s'il vous plaît? Si c'est une bonne fonctionnalité, pourquoi ne pas nous donner 10 ou 20 opérateurs infixes solides à définir à notre guise ?

Enfin, je pense qu'il est naturel de garder la tuyauterie exactement car elle est différente des autres applications de fonction. C'est une fonctionnalité, pas un bogue, qu'il est différent des autres conventions d'application des fonctions ; cette différence est ce qui lui permet de briller dans certains cas d'utilisation. Et il y a d'autres cas où (en agitant un peu la main) le nom vient avant le verbe, et beaucoup d'entre eux sont exactement du sucre syntaxique dans les cas où l'application de la fonction brute est difficile à manier. Du haut de ma tête, l'affectation x = 5 met le nom (symbole x ) avant le verbe (lier à une valeur). De même pour accéder aux champs de types t.a au lieu de getfield . Et plus profondément, l'indexation de tableau z[5] se lit comme "de z prend le 5ème élément" et est généralement plus naturelle que getindex(z, 5) .

Si c'est une bonne fonctionnalité, pourquoi ne pas nous donner 10 ou 20 opérateurs infixes solides à définir à notre guise ?

Il y a probablement plus que cela si l'on inclut tous ceux en unicode en plus de ceux en ASCII non réclamés comme <| , ++ , ...

Je ne lis pas tout le fil - mais je voulais juste dire
que j'adore pouvoir piper. Je voterais utile sur
cohérence tous les jours.

J'ai une très légère préférence pour le conserver, mais cela m'est égal tant qu'il reste un opérateur infixe. J'ai l'impression que je n'utiliserais probablement pas de tuyauterie de fonction si cela impliquait l'importation d'un package, ce qui me dit que je ne l'apprécie pas beaucoup.

Cela étant dit, je ne pense pas que cet argument du "principe de la moindre surprise" soit convaincant, car il émet certaines hypothèses sur une base d'utilisateurs diversifiée. Pour les locuteurs natifs des langages sujet-objet-verbe, je suppose que la majeure partie de la syntaxe de Julia viole le principe de la moindre surprise, et la canalisation des fonctions est plutôt confortable...

Ne pas lire tout le fil

😕

J'aime pouvoir piper

Encore une fois, je ne dis pas qu'il ne faut pas être capable de canaliser, mais plutôt que la fonctionnalité pourrait facilement être obtenue à la place dans l'un des nombreux packages de tuyauterie existants. La suppression du tube de base permet aux packages de définir plus facilement leur propre sémantique de tube sans avoir à adhérer ou à rester cohérent avec tout ce que Base fournit.

dans Julia, vous ne pouvez pas définir vos propres opérateurs d'infixe

Ce n'est pas vrai; tout ce qui est analysé comme un opérateur infixe peut être défini ou redéfini. Comme l'a souligné Martinholters, <| et ++ sont également disponibles, entre autres.

Je suis un peu neutre sur celui-ci, mais j'appuierai le sentiment que |> étant à l'envers par rapport à la syntaxe d'appel de fonction normale est tout l'intérêt . Même les plus grands fans de tuyauterie ne demandent pas (AFAIK) par exemple sin <| x parce que c'est vraiment redondant avec sin(x) . |> est pour les cas où il est plus facile pour les yeux et/ou le cerveau de penser à des données circulant de gauche à droite sans beaucoup de parenthèses.

J'aimerais que |> soit plus puissant, par exemple autoriser x |> f(_) + 2g(_) |> h etc. et qu'il ne soit pas simplement un opérateur. Chaque fois que quelqu'un a défini x |> f comme signifiant autre chose que f(x) , cela me fait vraiment trébucher parce que l'intérêt de l'opérateur tel que nous l'avons utilisé est qu'il s'agit d'une syntaxe d'appel d'ordre différent. Puisque nous pouvons surcharger l'appel, je ne vois pas de bonne raison pour que x |> f signifie autre chose.

@StefanKarpinski Des tuyaux plus puissants peuvent déjà être obtenus à l'aide de macros. Voir par exemple Pipe.jl , qui fournit exactement la syntaxe que vous décrivez. Tant que |> est un opérateur (personnellement, je ne vois pas |> comme un cas particulier), les macros peuvent utiliser n'importe quel délimiteur de tuyauterie qui analyse l'infixe, même s'il ne s'agit pas d'un :call . À titre d'exemple, on pourrait de la même manière utiliser @~ pour diriger (au moins à ce jour). Ce niveau de flexibilité est l'un des avantages de l'utilisation de macros dans Julia.

Nous pourrions ajouter la fonctionnalité de Pipe.jl au langage, et vous l'auriez alors sans avoir à écrire @pipe .

La principale raison de déprécier |> serait si nous voulons récupérer la syntaxe à d'autres fins que les gens préfèrent.

Je suppose que j'essaie de faire valoir que la tuyauterie n'a pas besoin de faire partie du langage, elle peut (et le fait déjà) vivre dans un package.

Mais s'il n'y a rien d'autre que nous voulons |> , je vois peu de mal à laisser sa définition (triviale) seule.

Je ne crois pas qu'il y ait actuellement de propositions pour réutiliser |> dans Base. Mon argument pour ne pas le définir dans Base est que cela nous donne plus de cohérence sans perte de fonctionnalité.

Des propositions ou des implémentations de packages "plus puissantes" seraient-elles simplifiées en n'ayant pas cette définition existante à s'inquiéter ou à contourner ?

@ararslan "Ce n'est pas vrai ; tout ce qui est analysé comme un opérateur infixe peut être défini ou redéfini."

Depuis le manuel "Opérateurs && et ||", ils sont analysés mais ne peuvent pas être redéfinis (c'est une bonne chose). Je crois que les seules exceptions.

Les soi-disant "opérateurs logiques" && et || sont infixes. [relation binaire unaire] "opérateur" est IMHO le terme incorrect pour eux car ils ne le sont pas. Non est une manière similaire à la logique bit à bit & et | qui permettent la surcharge (ce dont je ne suis pas sûr que ce soit un bon choix).

@PallHaraldsson Ce sont des flux de contrôle, pas des opérateurs dans le même sens que & , | , + , etc.

Essayons de rester sur le sujet ici si possible, s'il vous plaît.

@tkelman C'est un bon point. Je soupçonne cependant que nous pouvons rendre la future syntaxe de tuyauterie rétrocompatible. Par exemple, si _ est réservé, alors |> peut avoir une signification particulière lorsque ses arguments contiennent _ , et sinon faire la même chose que maintenant.

Il y a un autre problème : pour que |> fonctionne pour votre objet, définissez-vous |> ou "l'opérateur d'appel de fonction" (c'est-à-dire en y ajoutant des méthodes) ? Cela pourrait être plus propre si |> était une syntaxe intégrée pour l'appel de fonction, pour s'assurer que f(x) et x |> f sont toujours les mêmes.

Le consensus ici est très clairement contre, alors je vais continuer et clore le sujet. J'apprécie la discussion, tout le monde.

Je sais que ce sujet est clos. Je voulais juste dire "merci" d'avoir gardé l'opérateur.

Cette page vous a été utile?
0 / 5 - 0 notes