Julia: Opérateurs infixes personnalisés

Créé le 17 juin 2016  ·  65Commentaires  ·  Source: JuliaLang/julia

Il y a une discussion sur https://groups.google.com/forum/#!topic/julia -dev/FmvQ3Fj0hHs sur la création d'une syntaxe pour les opérateurs d'infixes personnalisés.

...

Modifié pour ajouter une note : @johnmyleswhite a souligné que le fil de commentaires ci-dessous est une invitation au bikeshedding. Veuillez vous abstenir de nouveaux commentaires à moins que vous n'ayez quelque chose de vraiment nouveau à ajouter. Il y a plusieurs propositions ci-dessous, marquées par des émoticônes "hourra" (cône qui explose). Vous pouvez utiliser ces icônes pour ignorer la discussion et simplement lire les propositions, ou pour trouver les différentes propositions afin de pouvoir voter "pouce vers le haut" ou "pouce vers le bas".

Les votes positifs/négatifs sur ce bogue dans son ensemble concernent la question de savoir si vous pensez que Julia devrait avoir un idiome infixe personnalisé. Les votes positifs / négatifs pour l'idée spécifique ci-dessous devraient aller sur le premier commentaire de @ Glen-O. (Le bogue avait 3 votes négatifs et 1 vote positif avant que cela ne soit clarifié.)

...

Proposition initiale (intérêt historique uniquement) :

La proposition qui semble avoir gagné est la suivante :

    a |>op<| b #evaluates (in the short term) and parses (in the long term) to `op(a,b)`

Afin d'avoir ce travail, il n'y a que des changements mineurs nécessaires :

  • Mettez la priorité de <| au-dessus de celle de |> , au lieu d'être la même.
  • Créez un groupe <| gauche à droite.
  • Créez la fonction <|(a,b...)=(i...)->a(i...,b...) . (comme indiqué dans le fil de discussion, cela aurait des utilisations autonomes, ainsi que son utilisation dans l'idiome ci-dessus)

Optionnel:

  • créer de nouvelles fonctions >|(a...,b)=(i...)->b(a...,i...) et |<(a,b...)=a(b...) avec les priorités et les regroupements appropriés.

    • Pipe first signifie évaluation, et pipe last la maintient en tant que fonction, tandis que > et < indiquent laquelle est la fonction.

  • créer de nouvelles fonctions >>|(a...,b)=(i...)->b(i...,a...) et <<|(a,b...)=(i...)->a(b...,i...) avec la priorité et le groupement appropriés.
  • créer des synonymes » , , et(/ou) pipe pour |> ; « , , et(/ou) rcurry pour <| ; et(/ou) lcurry pour <<| ; avec les synonymes à caractère unique fonctionnant comme des opérateurs infixes.
  • créez une macro @infix dans la base qui effectue le premier correctif d'analyseur ci-dessous.

Long terme:

  • apprenez à l'analyseur à changer a |>op<| b en op(a,b) , donc il n'y a pas de surcharge supplémentaire impliquée lors de l'exécution du code, et pour que les opérateurs puissent réellement être définis en position infixe. (Ceci est similaire à la façon dont l'analyseur traite actuellement le binaire a:b et le ternaire a:b:c différemment. Pour une personnalisation maximale, il devrait le faire pour les synonymes correspondants, mais pas pour les synonymes non correspondants, de sorte que par exemple a |> b « c serait toujours traité comme deux opérateurs binaires.)
  • apprenez à l'analyseur à comprendre les virgules et/ou les espaces afin que les ellipses dans les définitions ci-dessus fonctionnent comme prévu sans parenthèses supplémentaires.

(concerne https://github.com/JuliaLang/julia/issues/6946)

parser speculative

Commentaire le plus utile

Stefan n'est pas plus ancien que moi.

Tous les 65 commentaires

Faisant écho au fil julia-dev, je pense qu'il serait utile de citer le commentaire principal de Stefan sur cette proposition :

Juste pour définir les attentes ici, je ne pense pas qu'il y aura beaucoup d'"innovation syntaxique" avant Julia 1.0. (La seule exception à laquelle je peux penser est la nouvelle syntaxe d'appel vectorisée f.(v) .) Bien qu'il puisse être agréable d'avoir un moyen de faire en sorte que des fonctions arbitraires se comportent comme des opérateurs infixes, ce n'est tout simplement pas un problème urgent dans le langage.

En tant que personne ayant participé à une bonne partie de l'histoire du développement de Julia, je pense qu'il serait préférable de concentrer l'énergie sur les changements sémantiques plutôt que syntaxiques. Il reste beaucoup de problèmes sémantiques extrêmement importants à résoudre avant que Julia n'atteigne la version 1.0.

Notez en particulier que la mise en œuvre de cette fonctionnalité n'est pas simplement un diff ponctuel auquel seul l'auteur doit réfléchir : tout le monde devra réfléchir à la manière dont son travail interagit avec cette fonctionnalité à l'avenir, de sorte que le changement augmente réellement le long terme. charge de travail de chaque personne qui travaille sur l'analyseur.

Je pense que les commentaires de johnmyleswhite sont très appropriés concernant les changements d'analyseur "à long terme" suggérés. Mais les groupes "changements mineurs" et "optionnels" sont, pour autant que je sache, assez autonomes et à faible impact.

C'est-à-dire que les modifications de l'analyseur nécessaires pour activer la version minimale de cette proposition impliquent uniquement la priorité et le regroupement des opérateurs binaires normaux, le type de modifications qui sont plus ou moins routinières dans d'autres cas. Un développeur d'analyseur travaillant sur quelque chose sans rapport n'aurait pas plus besoin de garder une trace de cela qu'il n'a besoin de garder une trace de la signification de tous les nombreux opérateurs déjà existants.

Personnellement je trouve cette syntaxe assez laide et difficile à taper. Mais je suis d'accord qu'il serait bon d'avoir une syntaxe d'infixe plus générale.

Je pense que la bonne façon de penser à cela est comme un problème de syntaxe uniquement : ce que vous voulez, c'est utiliser op avec une syntaxe infixe, donc définir d'autres fonctions et opérateurs pour obtenir cela est un détour. En d'autres termes, tout devrait être fait dans l'analyseur.

J'envisagerais en fait de récupérer | pour cela et d'utiliser a |op| b . On peut dire que la syntaxe d'infixe générale est plus importante que bitwise or. (Nous avons déjà parlé de la récupération des opérateurs au niveau du bit ; ils semblent être un peu un gaspillage de syntaxe tel qu'il est.)

a f b est disponible en dehors des syntaxes de concaténation de tableau et d'appel de macro.

a f b peut fonctionner, mais il semble assez fragile. Imaginez essayer d'expliquer à quelqu'un pourquoi a^2 f b^2 f c^2 est légal mais a f b c et a+2 f b+2 f c+2 ne le sont pas. (Je sais, ce dernier suppose que la priorité est prec-times, mais quelle que soit la priorité, ce genre de chose général est une préoccupation).

Quant à a |op| b : au départ, j'étais favorable à une proposition similaire, a %op% b , comme vous pouvez le voir dans le fil de discussion des groupes Google. Mais la bonne chose à propos des |> et <| proposés est qu'ils sont chacun individuellement utiles en tant qu'opérateurs binaires, et ils se combinent naturellement pour fonctionner comme vous le souhaitez (étant donné la bonne priorité et le bon regroupement, c'est-à-dire. ) Cela signifie que vous pouvez l'implémenter à court terme en utilisant les mécanismes d'analyseur existants, et ainsi éviter de créer des maux de tête pour les développeurs d'analyseurs à l'avenir, comme je l'ai dit dans ma réponse à johnmyleswhite ci-dessus.

Donc, bien que j'aime a |op| b et que je ne m'y opposerais certainement pas, je pense que nous devrions chercher un moyen d'avoir deux opérateurs différents pour simplifier les changements d'analyseur requis. Si nous recherchons une typabilité maximale et que nous ne nous opposons pas à ce que | signifie "tuyau" plutôt que "ou au niveau du bit", alors qu'en est-il de a |op\\ b ou a |op& b ?

"Maux de tête pour les développeurs d'analyseurs" est la préoccupation la plus faible possible.

"Maux de tête pour les développeurs d'analyseurs" est la préoccupation la plus faible possible.

En tant que développeur d'analyseurs, je suis sans équivoque d'accord avec cela.

|> et <| sont tous les deux de très bons opérateurs d'infixe, mais il n'y a aucun avantage à implémenter une syntaxe d'opérateur générale en utilisant deux autres opérateurs. Et il reste encore beaucoup à dire sur la façon dont cette syntaxe est verbeuse et peu attrayante.

il n'y a aucun avantage à implémenter une syntaxe d'opérateur générale à l'aide de deux autres opérateurs.

Pour être clair, la vision à long terme ici est qu'il y aurait binaire f <| y , binaire x |> f et ternaire x |> f <| z , où le premier n'est qu'une fonction mais le second deux sont implémentés sous forme de transformations dans l'analyseur.

L'idée que cela pourrait être implémenté en utilisant deux fonctions ordinaires |> et <| n'est qu'un pont temporaire vers cette vision.

Et il reste encore beaucoup à dire sur la façon dont cette syntaxe est verbeuse et peu attrayante.

C'est un bon point. Que diriez-vous de remplacer |> et <| par | et & ? Ils ont du sens à la fois en couple et individuellement, bien qu'ils puissent être un peu choquants pour un joueur de hockey un peu.

Voler à la fois | et & pour cela ne serait pas une bonne allocation d'ASCII, et je soupçonne que beaucoup préféreraient que les délimiteurs soient symétriques.

Si les gens veulent un opérateur ternaire x |> f <| y pour d'autres raisons, c'est bien, mais je pense qu'il devrait être considéré séparément. Je ne suis pas sûr que l'analyseur devrait transformer |> en <| . D'autres opérateurs similaires comme < ne fonctionnent pas de cette façon. Mais c'est aussi une question distincte.

Voler les deux | et & pour cela ne serait pas une bonne allocation d'ASCII, et je soupçonne que beaucoup préféreraient que les délimiteurs soient symétriques.

D'ACCORD.

Je comprends que > et < sont difficiles à taper. En termes de symétrie et de typabilité sur un clavier standard, je suppose que le plus simple pourrait être quelque chose comme &% et %& , mais c'est vraiment moche, R parallèle ou non. /| et |/ pourraient également valoir la peine d'être envisagés.

...

Je ne suis pas sûr que l'analyseur devrait transformer |> en un <| retourné

Je pense que tu as mal compris. a |> b devrait être analysé en b(a) . (La version sans analyse spéciale serait ((x,y)->y(x))(a,b) , ce qui donne la même chose, mais avec plus de surcharge.)

a |> b doit analyser en b(a)

Ah, d'accord, j'ai compris.

Je pense que nous pourrions parler des personnages à utiliser pendant des années. Je ferais confiance à @StefanKarpinski (en tant que personne la plus expérimentée dans cette conversation jusqu'à présent) pour rendre une décision, et cela me conviendrait. Même si c'est quelque chose que j'ai contesté (comme a f b .)

Voici quelques options pour voir ce qui plaît :
a |>op<| b (laissant |> actuel inchangé)
a |{ op }| b (à proximité et même état de décalage sur de nombreux claviers courants, pas trop moche. Un peu étrange en tant qu'autonomes.)
a \| op |\ b ou a /| op |/ b ou leurs combinaisons
a $% op %$ b (relativement typable, inspiré de R. Mais un peu moche.)
a |% op %| b
a |- op -| b
a |: op :| b
a | op \\ b
a | op ||| b
a op b

Stefan n'est pas plus ancien que moi.

On dirait que vous venez de vous nommer, alors, pour les pouvoirs de BDFL sur cette question ! ;)

a @op@ b ?

Je suppose que mon vote est d'utiliser les 4 de \| , |\ , /| et |/ . Bas pour l'évaluation, haut pour le curry ; barre vers la fonction. Alors:
a \| f (ou f |/ a ) -> f(a)
a /| f (ou f |\\ a ) -> (b...)->f(a,b...)
f |\ b (ou b //| f ) -> (a...)->f(a...,b)
Et ainsi:
a \| f |\ b (ou a /| f |/ b ) -> f(a,b)
a \| f |\ b |\ c (ou a /| b /| f |/ c ) -> f(a,b,c)

Chacun des 4 opérateurs principaux, sauf peut-être |/ , est utile en soi. La redondance serait certainement non-Pythonic, mais je pense que la netteté logique est Julian. Et en pratique, vous pouvez utiliser n'importe quelle version de l'idiome infixe que vous trouvez plus facile à taper ; ils sont tous deux également lisibles, en ce sens qu'une fois que vous en avez appris un, vous comprenez naturellement les deux.

De toute évidence, il serait tout aussi logique que vous permutiez toutes les barres obliques, de sorte que les flèches vers le haut soient pour l'évaluation et vers le bas pour le curry.

J'attends toujours des nouvelles d'On High (et je m'excuse pour ma maladresse de débutant à deviner ce que cela signifiait). Mais si quelqu'un de plus grand que ce hangar à vélos rend une décision, pour cette version ou toute autre version avec au moins deux nouveaux symboles, je serais heureux d'écrire un patch à court terme (en utilisant des fonctions) et/ou un patch approprié (en utilisant des transformations).

Nous essayons d'éviter d'avoir un BDFL dans la mesure du possible :)

Je pensais juste noter quelques petites choses rapidement.

Premièrement, l'autre avantage (les "utilisations autonomes") de la notation proposée est que <| peut être utilisé dans d'autres contextes, d'une manière qui améliore la lisibilité. Par exemple, si vous avez un tableau de chaînes, A , et que vous voulez les remplir toutes à gauche à 10, maintenant, vous devez écrire map(i->lpad(i,10),A) . C'est relativement difficile à lire. Avec cette notation, cela devient map(lpad<|10,A) , ce qui, je pense que vous en conviendrez, est nettement plus propre.

Deuxièmement, l'idée derrière cela est de garder la notation cohérente. Il existe déjà un opérateur |> , qui existe pour changer le "fixe" d'un appel de fonction du préfixe au suffixe. Cela ne fait qu'étendre la notation.

Troisièmement, la possibilité d'utiliser l'infixe direct comme a f b pose un plus gros problème. a + b et a * b finiraient par avoir la même priorité, puisque + et * sont des noms de fonction, et il serait impossible pour le système de ont une priorité variable. Cela, ou il faudrait traiter différemment les opérateurs infixes existants, ce qui pourrait prêter à confusion.

Par exemple, si vous avez un tableau de chaînes, A , et que vous voulez les remplir toutes à gauche à 10, maintenant, vous devez écrire map(i->lpad(i,10),A) . C'est relativement difficile à lire. Avec cette notation, cela devient map(lpad<|10,A) , ce qui, je pense que vous en conviendrez, est nettement plus propre.

Je ne suis absolument pas d'accord. La syntaxe proposée est – pardonnez-moi – salade ASCII, frôlant certaines des pires offenses de Perl et APL, sans précédent dans d'autres langages pour donner au lecteur occasionnel un indice de ce qui se passe. La syntaxe actuelle, bien que quelques caractères plus longs (cinq ?), Est assez claire pour quiconque sait que i->expr est une syntaxe lambda - ce qui est le cas dans un ensemble de langues vaste et croissant.

a + b et a * b finiraient par avoir la même priorité, puisque + et * sont des noms de fonction, et il serait impossible pour le système d'avoir une priorité variable. Cela, ou il faudrait traiter différemment les opérateurs infixes existants, ce qui pourrait prêter à confusion.

Je ne pense pas que ce soit un vrai problème; nous pouvons simplement dire quelle est la priorité de l'infixe a f b et conserver également tous les niveaux de priorité existants. Cela fonctionne car la priorité est déterminée par le nom de la fonction ; toute fonction appelée "+" aura la priorité "+".

Oui, nous le faisons déjà pour la syntaxe 1+2 in 1+2 , et cela n'a pas posé de problème.

Je ne pense pas que ce soit un vrai problème; nous pouvons simplement dire quelle est la priorité de l'infixe afb et conserver également tous les niveaux de priorité existants. Cela fonctionne car la priorité est déterminée par le nom de la fonction ; toute fonction appelée "+" aura la priorité "+".

Je ne voulais pas dire qu'il est difficile d'écrire l'analyseur pour le faire fonctionner. Je voulais dire que cela conduit à des problèmes de cohérence, d'où moi disant "ou il faudrait traiter différemment les opérateurs infixes existants, ce qui pourrait prêter à confusion". Entre autres choses, considérez que ¦ et n'ont pas l'air si différents dans leur concept, pourtant l'un est un opérateur infixe prédéfini, tandis que l'autre ne l'est pas.

Je ne suis absolument pas d'accord. La syntaxe proposée est – pardonnez-moi – salade ASCII, frôlant certaines des pires offenses de Perl et APL, sans précédent dans d'autres langages pour donner au lecteur occasionnel un indice de ce qui se passe. La syntaxe actuelle, bien que plus longue de quelques caractères (cinq ?), est assez claire pour quiconque sait que i-> expr est une syntaxe lambda - ce qui est le cas dans un ensemble de langues vaste et croissant.

Peut-être devrais-je être plus clair sur ce que je dis. Je dis que pouvoir décrire l'opération comme "lpad par 10" est beaucoup plus clair que i->lpad(i,10) ne le fait. Et à mon avis, lpad<|10 est le plus proche de cela, sous une forme non spécifique au contexte.

Il serait peut-être utile que je décrive d'où je viens. Je suis avant tout un mathématicien et un physicien mathématicien, et la "syntaxe lambda", bien que sensible du point de vue de la programmation, n'est pas la plus claire pour ceux qui sont moins expérimentés en programmation. Julia est, si je comprends bien, principalement destinée à être un langage de calcul scientifique, d'où la forte ressemblance avec MATLAB.

Je dois demander - comment lpad<|10 est-il plus "salade ASCII" que, disons, x|>sin|>exp ? Pourtant, la notation |> a été ajoutée. Comparez avec, disons, les scripts bash, où | est utilisé pour passer l'argument de gauche à la commande de droite - si vous savez que cela s'appelle "pipe", cela a _un peu_ plus de sens, mais si vous n'êtes pas doué en programmation, cela n'aura aucun sens. À cet égard, |> a en fait plus de sens, car il ressemble vaguement à une flèche. Et puis <| est une extension naturelle de la notation.

Comparez avec certaines des autres suggestions, telles que %func% , qui _a_ un précédent dans une autre langue, mais qui est complètement opaque pour les personnes qui n'ont pas une connaissance approfondie de la programmation dans la langue.

Remarquez, j'ai regardé un peu en arrière l'une des discussions les plus anciennes, et je vois qu'il y a eu une notation utilisée dans une autre langue qui serait plutôt sympa, en théorie. Haskell utilise apparemment a |> b c d pour représenter b(a,c,d) . Si les espaces suivant un nom de fonction vous permettaient de spécifier des "paramètres" de cette manière, cela fonctionnerait bien - map(lpad 10,A) . Le seul problème se pose avec les opérateurs unaires - map(+ 10,A) produirait une erreur, par exemple, car il interpréterait à "+10" au lieu de i->+(i,10) .

Sur a f b : les problèmes de priorité ne sont peut-être pas aussi graves que Glen-O l'a suggéré, mais à moins que les fonctions d'infixation définies par l'utilisateur n'aient la priorité la plus faible, elles existent. Dites, pour les besoins de l'argument, nous leur donnons des prec-times. Dans ce cas,
a^2 f b^2 => f(a^2,b^2)
a+2 f b+2 => a+f(2,b)+2
a^2 f^2 b^2 => (f^2)(a^2,b^2)
a f+2 b => erreur de syntaxe ?

Tout cela est une conséquence naturelle de la façon dont vous écrivez l'analyseur, donc ce n'est pas particulièrement un casse-tête dans ce sens. Mais ce n'est pas particulièrement intuitif pour l'utilisateur occasionnel de l'idiome.

Sur l'utilité d'un idiome curry
Je suis d'accord avec Glen-O que (i)->lpad(i,10) est tout simplement pire que lpad<|10 (ou, si nous le choisissons, lpad |\ 10 , ou autre). Le i est une charge cognitive entièrement étrangère et une source potentielle d'erreurs ; en fait, je jure que lorsque je tapais ça tout à l'heure, j'ai involontairement tapé (i)->lpad(x,10) initialement. Donc, avoir une opération de curry infixe me semble être une bonne idée.
Cependant, si telle est l'intention, alors quel que soit l'idiome infixe sur lequel nous nous installons, nous pouvons créer notre propre opération de curry. Si c'est a f b , alors quelque chose comme lpad rcurry 10 serait bien. Le point est la lisibilité, pas les frappes. Je pense donc que ce n'est qu'un argument faible pour <| .

Sur a |> b c d
J'aime beaucoup cette proposition. Je pense que nous pourrions faire en sorte que |> acceptent les espaces de chaque côté, donc a b |> f c d => f(a,b,c,d) .

(Remarque : Si ma suggestion de a b |> f c d et celle de Glen-O de map(lpad 10,A) , cela crée un cas particulier : (a b) |> f c d => f((x)->a(x,b),c,d) . Mais je pense que c'est tolérable.)

Cela a toujours des problèmes similaires en termes de priorité des opérateurs comme a f b . Mais d'une manière ou d'une autre, je pense qu'ils sont plus tolérables si vous pouvez au moins en parler en termes de priorité de l'opérateur |> , plutôt que d'être la priorité de l'opérateur ternaire de avec .

Essayez lpad.(["foo", "bar"], 10) sur 0.5. Le |> existant n'est pas exactement aimé de tous.

@tkelman : Je vois le problème, mais où voulez-vous en venir ? Vous pensez que nous devrions corriger le |> existant avant de lui ajouter des utilisations supplémentaires ? Si c'est le cas, comment?

Personnellement, je pense que nous devrions nous débarrasser du |> existant.

Essayez lpad.(["foo", "bar"], 10) sur 0.5. Le |> existant n'est pas exactement aimé de tous.

Je pense que vous avez raté le point. Oui, la notation func.() est agréable et contourne le problème dans certaines situations. Mais j'utilise la fonction map comme simple démonstration. Toute fonction qui prend une fonction comme argument bénéficierait de cette configuration. À titre d'exemple, uniquement pour illustrer mon propos, vous voudrez peut-être trier certains nombres en fonction de leur multiple le moins commun avec un numéro de référence. Lequel semble plus net et plus facile à lire : sort(A,by=i->lcm(i,10)) ou sort(A,by=lcm 10) ?

Je voudrais noter une fois de plus que toute façon de définir les opérateurs infixes permettra de créer un opérateur qui fait ce que Glen-O veut que <| fasse, de sorte qu'au pire il pourra écrire quelque chose comme sort(A,by=lcm |> currywith 10) . Le but de cette page est de discuter de la façon de faire des a ... f ... b => f(a,b) . Je comprends que le fait que les |> existants ou les <| proposés soient des opérateurs valables a une certaine relation à ce point, mais essayons de ne pas trop nous laisser distraire.

Personnellement, je pense que la proposition a |> b c est la meilleure jusqu'à présent. Il suit une convention existante de Haskell ; il est logiquement lié à l'opérateur |> existant ; il est à la fois raisonnablement lisible et raisonnablement facile à taper. Le fait que j'ai l'impression qu'elle s'étende naturellement à d'autres usages est secondaire. Si vous n'êtes pas d'accord, veuillez au moins mentionner vos sentiments sur l'idiome de base, pas seulement sur les utilisations secondaires proposées.

Je voulais dire que cela conduit à des problèmes de cohérence, d'où moi disant "ou il faudrait traiter différemment les opérateurs infixes existants, ce qui pourrait prêter à confusion".

Je suis d'accord qu'il est difficile de décider de la priorité pour a f b . Par exemple, in bénéficie clairement de la priorité de comparaison, mais il est fort probable que de nombreuses fonctions utilisées comme infixe ne voudront pas de priorité de comparaison. Cependant, je ne vois aucun problème de cohérence. Différents opérateurs ont une priorité différente. L'ajout a f b ne nous force pas à donner à + et * la même priorité.

Notez que |> a déjà la priorité adjacente à la comparaison. Pour toute autre priorité, franchement, je pense que les parenthèses sont acceptables.

Si vous n'êtes pas d'accord avec moi, et si nous utilisions a |> f b , alors il pourrait y avoir des opérateurs similaires |+> , |*> et |^> , qui fonctionnait de la même manière que |> , mais avait la priorité de leur opérateur central. Je pense que c'est exagéré mais c'est une possibilité.

Une autre possibilité pour résoudre le problème de priorité est d'utiliser une syntaxe pour les opérateurs d'infixes personnalisés qui inclut des parenthèses d'un certain type, par exemple (a f b) .

Discussions connexes : https://github.com/JuliaLang/julia/issues/554 , https://github.com/JuliaLang/julia/issues/5571 , https://github.com/JuliaLang/julia/pull/14476 , https://github.com/JuliaLang/julia/issues/11608 et https://github.com/JuliaLang/julia/issues/15612.

Je dois demander - en quoi lpad<|10 est-il plus "salade ASCII" que, disons, x|>sin|>exp ? Pourtant, la notation |> a été ajoutée.

J'imagine que @tkelman se dispute

nous devrions nous débarrasser du |> existant.

en partie parce que _both_ lpad<|10 et x|>sin|>exp s'aventurent dans le territoire de la salade ASCII :).

Je pense que le (a f b) de @toivoh , avec des parenthèses obligatoires, est la meilleure proposition à ce jour.

Lié à https://github.com/JuliaLang/julia/issues/11608 (et donc aussi https://github.com/JuliaLang/julia/issues/4882 et https://github.com/JuliaLang/julia/pull /14653) : Si (a f b) => f(a,b) , alors cela aurait du sens si (a <strong i="8">@m</strong> b) => (<strong i="10">@m</strong> a b) . Cela permettrait de remplacer la logique de macro de cas spéciaux existante pour y ~ a*x+b par $# (y @~ a*x+b) 5$#$ normal (et donc beaucoup plus transparent).

En outre, les "parenthèses requises" pourraient être l'idiome préféré pour les définitions d'infixes concises. Au lieu de dire (pour utiliser un exemple stupide) a + b = string(a) * string(b) , vous seriez encouragé (par des outils lint ou par des avertissements du compilateur) à dire (a + b) = string(a) * string(b) . Je me rends compte que ce n'est pas en fait une conséquence directe du choix de l'option "parenthèses requises" pour l'infixe, mais c'est un idiome pratique qui nous permettrait d'avertir les personnes utilisant l'infixe sur le LHS par erreur mais de licencier les personnes qui le font sur but.

Mon sentiment est actuellement que si vous appliquez un infixe de fonction (plutôt qu'un préfixe),
alors c'est un opérateur, et devrait ressembler et agir comme un opérateur.

Et nous prenons en charge les opérateurs infixes définis à l'aide d'Unicode.
depuis https://github.com/JuliaLang/julia/issues/552

Je suppose qu'il serait peut-être bon d'avoir cette étendue afin que vous puissiez ajouter les mots-clés comme dans la suggestion originale.
Nous pourrions donc avoir, par exemple, 1 ⊕₂ 1 == 0
Pouvoir avoir des noms arbitraires pour votre infixe semble un peu excessif.

devrait ressembler et agir comme un opérateur.

Je suis d'accord qu'il devrait y avoir des conventions de nommage fortes pour les opérateurs infixes. Par exemple : un caractère d'unicode, ou se termine par une préposition. Mais il doit s'agir de conventions qui se développent de manière organique, et non d'exigences imposées par le compilateur. Certes, je ne pense pas que le #552 soit la fin de l'histoire ; s'il existe des dizaines d'opérateurs codés en dur, il devrait y avoir un moyen d'en ajouter davantage par programmation, ne serait-ce que pour le prototypage de nouvelles fonctionnalités.

...

Pour moi, la proposition (a f b) (et (a <strong i="10">@m</strong> b) ) est au-dessus des autres propositions de ce bogue. Je suis presque tenté de faire un patch.

(a f b) => f(a,b)
(a f b c d) => f(a,b,c,d)
(a f) =>erreur de syntaxe
(a+2 f+2 b+2) => (f+2)(a+2,b+2)
(t1=a t2=f t3=b) => (t1=f)((t2=a),(t3=b)) (l'espace a la priorité la plus faible possible, comme dans les macros)

...

Serait-il inapproprié que je soumette un patch ?

Je n'ai pas compris les deux derniers cas :

(a+2 f+2 b+2)=>(f+2)(a+2,b+2)
(t1=a t2=f t3=b)=>(t1=f)((t2=a),(t3=b))

Je trouve la syntaxe (a f b c d) très étrange. Étant donné que 1 + 2 + 3 peut être écrit comme +(1,2,3) , alors f(a,b,c) ne devrait-il pas être écrit comme (a f b f c) ?

Dans l'ensemble, je ne suis personnellement pas convaincu que Julia devrait prendre en charge les opérateurs d'infixes personnalisés au-delà de ce qui est actuellement autorisé.

Je peux voir deux problèmes avec (a f b c d) .

Tout d'abord, il sera difficile de lire lorsque vous avez une expression plus compliquée - l'une des raisons pour lesquelles les parenthèses peuvent être frustrantes est qu'il peut souvent être difficile de dire, d'un coup d'œil, quelles parenthèses s'associent à d'autres parenthèses. C'est pourquoi les opérateurs d'infixe et de suffixe ( |> ) sont souhaitables en premier lieu. La postfixation est particulièrement appréciée car elle permet une lecture agréable et soignée de gauche à droite sans avoir à gérer les parenthèses à chaque fois.

Deuxièmement, cela ne laisse aucun moyen de bien faire des choses comme le faire élément par élément. Ma compréhension est que f.(a,b) va être une notation en 0.5 pour faire fonctionner f élément par élément sur ses arguments avec la diffusion. Il n'y aura pas de moyen astucieux de faire la même chose avec la notation infixe, si c'est (a f b) . Au mieux, cela devrait être (a .f b) , ce qui, à mon avis, perd la gentillesse de la symétrie que .( offre avec .+ et .* .

Comparez, par exemple, le cas de vouloir utiliser l'exemple de Haskell. shashi sur # 6946 a fait remarquer qu'il a un équivalent ici. En Haskell, vous écririez circle 10 |> move 0 0 |> animate "scale" "ease" . En utilisant cette notation, cela devient ((circle(10) move 0 0) animate "scale" "ease") , ce qui n'est pas plus clair que animate(move(circle(10),0,0),"scale","ease") . Et si vous vouliez copier votre cercle à plusieurs endroits, en utilisant la notation |> , vous pourriez avoir circle 10 .|> copy [1 15 50] [3 14 25] . À mon avis, c'est la meilleure façon de mettre en œuvre l'idée - et ensuite, les crochets font leur rôle normal de traiter les problèmes d'ordre des opérations.

Et comme je l'ai souligné, a|>f b c a également l'avantage d'avoir une extension naturelle permettant à la même notation d'avoir plus d'utilisation - f b c serait analysé comme "fonction f avec paramètres b et c définis), et serait donc équivalent à i->f(i,b,c) . Cela lui permet de fonctionner non seulement pour l'infixation, mais pour d'autres situations où vous pourriez vouloir passer une fonction (en particulier une fonction intégrée) avec des paramètres (en notant que la norme est d'avoir l'objet de la fonction en premier).

Le |> indique également clairement quelle est la fonction. Si vous aviez, disons, (tissue wash fire dirty metal) , il serait assez difficile, d'un coup d'œil, de reconnaître wash comme fonction. D'un autre côté, tissue|>wash fire dirty metal a un gros indicateur disant " wash est la fonction".

Certaines des dernières objections me semblent dire "mais vous pourriez abuser de cette fonctionnalité!" Ma réponse est : bien sûr que vous pourriez. Vous pourriez déjà écrire du code totalement illisible en utilisant des macros si vous le vouliez. Le travail de l'analyseur consiste à permettre des utilisations légitimes ; pour arrêter les abus, nous avons des conventions/idiomes et dans certains cas des délires. Spécifiquement:

Je n'ai pas compris les deux derniers cas :

Ceux-ci ne sont en aucun cas destinés à être un exemple à suivre; ils ne font que montrer les conséquences naturelles des règles de préséance. Je pense que les deux derniers exemples seraient considérés comme abusant de la syntaxe, bien que (a^2 ಠ_ಠ b^2) => ಠ_ಠ(a^2,b^2) soit assez clair.

f(a,b,c) ne devrait-il pas être écrit comme (afbfc)

Ma proposition de (a f b c d) était, franchement, une réflexion après coup. Je pense que c'est logique, et je pourrais trouver des exemples où c'est utile, mais je ne veux pas raccrocher cette proposition sur cette question si elle est controversée.

[Par exemple:

  • f est une "méthode objet" d'un objet a, probablement compliquée, utilisant b, c et d, probablement plus simple.
  • f est une méthode "diffusée naturellement" comme push! ]

Alors que (a f b f c) aurait du sens si f ressemblait à + , je pense que la plupart des opérateurs ne sont pas réellement comme + , donc cela ne devrait pas être notre modèle.

il sera difficile à lire lorsque vous avez une expression plus compliquée

Encore une fois, ma réponse serait "alors n'en abusez pas".

Disons que nous voulons un moyen d'écrire une expression compliquée comme a / (b + f(c,d^e)) avec l'infixe f . Dans la proposition de @toivoh , ce serait a / (b + (c f d^e)) . Dans une utilisation similaire à Haskell, ce serait a / (b + (c |> f d^e)) ou au "meilleur", si la priorité |> était modifiée pour corriger cet exemple particulier, a / (b + c |> f d^e) . Je pense que celui de @toivoh est facilement aussi bon ici.

(tissue wash fire dirty metal)

Je pense que la solution à cela est de fortes conventions de nommage pour les opérateurs infixes. Par exemple, s'il y avait une convention selon laquelle les opérateurs infixes devraient un caractère d'unicode, ou se terminer par une préposition ou un trait de soulignement, alors ce qui précède serait quelque chose comme (tissue wash_ fire dirty metal) qui est aussi clair que cette expression pourrait jamais espérer être .

...

élément par élément

C'est une préoccupation valable. (a .f b) est une mauvaise idée, car il pourrait être lu comme ((a.f) b) . Ma première suggestion est (a ..f b) mais ça ne me fait pas très plaisir.

circle 10 |> move 0 0 |> animate "scale" "ease"

J'ai utilisé jquery, donc je vois vraiment l'avantage d'un chaînage de fonctions comme ça. Mais je pense que ce n'est pas le même problème que les opérateurs infixes. En utilisant la proposition (a f b) , vous pourriez écrire ce qui précède comme suit :

circle 10 |> (move <| 0 0) |> (animate <| "scale" "ease")

... qui n'est pas aussi concis que la version Haskell, mais qui reste assez lisible.

Peut-être que cela peut être limité à seulement trois choses à l'intérieur du () :
(a f (b,c))
.(a f (b,c)) en utilisant l'opérateur .(

Enfin, une réponse au point général :

Dans l'ensemble, je ne suis personnellement pas convaincu que Julia devrait prendre en charge les opérateurs d'infixes personnalisés au-delà de ce qui est actuellement autorisé.

Évidemment, nous avons tous droit à nos opinions. (Je ne sais pas si le pouce levé faisait référence à cette partie du commentaire, mais si c'est le cas, cela va tripler.)

Mais mes contre-arguments sont :

  • Julia a déjà des dizaines d'opérateurs infixes, dont beaucoup sont extrêmement spécialisés. Il est inévitable que d'autres soient proposés. Quand quelqu'un dit "comment pouvez-vous avoir mais pas § ?", je préfère répondre "faites-le vous-même" et non "attendez que la prochaine version soit largement adoptée".
  • Quelque chose comme (a § b) est éminemment lisible, et la syntaxe est suffisamment légère pour apprendre à partir d'un ou deux exemples.
  • Je ne suis pas la première personne à soulever ce problème, et je ne serai pas la dernière. Je comprends que les concepteurs de langages doivent être très sceptiques à l'égard des (mauvaises) fonctionnalités rampantes, car une fois que vous avez ajouté une fonctionnalité laide, il est fondamentalement impossible de la corriger plus tard. Mais comme je l'ai dit plus haut, je pense que (a f b) est suffisamment propre pour que vous ne le regrettiez pas.

Je ne suis vraiment pas sûr de la clarté de (a f b)

Voici un cas d'utilisation possible :
select((((:emp_id, :last_name) from employee_tbl) where (:city, == ,'indianapolis')) orderby :emp_id));

C'est certainement une utilisation viable des fonctions infixes.
La fonction select est soit la fonction d'identité, soit envoie la requête construite à la base de données.

Est-ce que ce code est clair ?
Je ne sais pas.

.(a f b)

Oui, c'est logique. Mais ce n'est pas très lisible.

Est-ce (a @. f b) est plus lisible ? Parce que la macro @. pour activer cela serait une simple ligne.

[[[En y repensant, si nous autorisions les macros infixes sans nécessiter de parenthèses, @Glen-O pourrait les utiliser pour faire ce qu'il veut : circle(10) @> move 0 0 @> animate "scale" "ease" => @> (@> circle(10) move 0 0) animate "scale" "ease" =macro> animate(move(circle(10),0,0),"scale","ease") . Je pense que cette solution est plus laide que (a f b) , mais au moins cela résoudrait ce bogue global à mes yeux.]]]

...

select((((:emp_id, :last_name) from employee_tbl) where (:city, = ,'indianapolis')) orderby :emp_id);

Je préférerais certainement utiliser une macro pour "où" afin que l'expression conditionnelle n'ait pas à être étrangement citée. Alors:

select((((:emp_id, :last_name) from employee_tbl) <strong i="22">@where</strong> city == 'indianapolis') orderby :emp_id);

Les parenthèses sont légèrement ennuyeuses, mais d'un autre côté, je ne vois aucun moyen raisonnable pour l'analyseur de traiter ce type d'expression sans elles.

select((((:emp_id, :last_name) from employee_tbl) <strong i="6">@where</strong> city == 'indianapolis') orderby :emp_id);

Les parenthèses sont légèrement ennuyeuses, mais d'un autre côté, je ne vois aucun moyen raisonnable pour l'analyseur de traiter ce type d'expression sans elles.

À la réflexion, la priorité dans cette expression est juste de droite à gauche. Ainsi, en utilisant des macros infixes, cela pourrait tout aussi bien être :

select((:emp_id, :last_name) <strong i="11">@from</strong> employee_tbl <strong i="12">@where</strong> city == 'NYC' <strong i="13">@orderby</strong> :emp_id)

ou même:

<strong i="17">@select</strong> (:emp_id, :last_name) <strong i="18">@from</strong> employee_tbl <strong i="19">@where</strong> city == 'NYC' <strong i="20">@orderby</strong> :emp_id

Donc, bien que j'aime toujours (a f b) , je commence à voir que les macros infixes sont aussi une bonne réponse.

Voici la proposition complète à travers des exemples, suivis des avantages et inconvénients :

utilisations principales :

  • a <strong i="28">@m</strong> b => <strong i="30">@m</strong> a b
  • a <strong i="33">@m</strong> b c => <strong i="35">@m</strong> a b c
  • a <strong i="38">@m</strong> b <strong i="39">@m2</strong> c => <strong i="41">@m2</strong> (<strong i="42">@m</strong> a b) c
  • <strong i="45">@defineinfix</strong> f; a <strong i="46">@f</strong> b => macro f(a,b...) :(f($a,$b...)) end; <strong i="48">@f</strong> a b => f(a,b)

Cases d'angle : (non destinées à être un bon code, juste pour montrer comment l'analyseur fonctionnerait)

  • t1=a <strong i="54">@m</strong> t2=b t3=c => <strong i="56">@m</strong> t1=a t2=b t3=c (bien que ce ne soit pas un bon style de programmation)
  • t1 + a <strong i="59">@m</strong> t2 + b => <strong i="61">@m</strong> t1+a t2+b (bien que ce ne soit pas un bon style de programmation)
  • a b <strong i="64">@m</strong> c => erreur de syntaxe (??)
  • a <strong i="67">@m</strong> b [c,d] => s'il vous plaît ne le faites pas, mais <strong i="70">@m</strong> a b[c,d] (ETA : Non, avec le patch, cela donne <strong i="72">@m</strong> a b ([c,d]) , ce qui est probablement mieux.)
  • a <strong i="75">@m</strong> b ([c,d]) => <strong i="77">@m</strong> a b ([c,d])
  • [a <strong i="80">@m</strong> b] => mauvais style, veuillez utiliser des parenthèses pour clarifier, mais [a (<strong i="83">@m</strong> b)] (??)
  • a @> f b => @> a f b => f(a,b)
  • <strong i="90">@outermacro</strong> a b <strong i="91">@m</strong> c d => <strong i="93">@outermacro</strong> a (<strong i="94">@m</strong> b c d)

Avantages :

  • définir des macros infixes, obtenir des fonctions infixes gratuitement (avec une surcharge unique de l'évaluation de la macro. Ce n'est pas aussi faible que la magie de l'analyseur, mais bien mieux que d'avoir des appels de fonction supplémentaires à chaque évaluation)
  • peut conduire à de puissants DSL, comme le montre l'exemple de type SQL ci-dessus
  • Supprime le besoin d'un opérateur |> séparé, puisqu'il s'agit d'une macro à une ligne. De même pour <| et le reste des propositions de @Glen-O.
  • explicite, donc très faible risque d'être utilisé par accident, contrairement à (a f b)
  • Comme indiqué, la macro @defineinfix pourrait permettre l'utilisation de raccourcis pour les fonctions et non les macros.

Inconvénients (mineurs):

  • la priorité et le regroupement semblent bien fonctionner dans la plupart des cas avec RtoL, mais il y aurait des exceptions qui nécessiteraient des parenthèses.
  • Je pense que a @> f b ou même a <strong i="112">@f</strong> b n'est pas aussi lisible que (a f b) (bien qu'ils ne soient pas trop horribles non plus.)

Étant donné à quel point ce fil est devenu actif, je vais rappeler aux gens ma préoccupation initiale à ce sujet : les problèmes de syntaxe génèrent souvent une énorme quantité d'activité, mais cette quantité d'activité est généralement hors de proportion avec la valeur à long terme du changement débattu. En grande partie, c'est parce que les discussions sur la syntaxe finissent par être proches de purs arguments sur les goûts.

cette quantité d'activité est généralement hors de proportion

Je suis désolé. Je suis probablement le plus coupable de faire des allers-retours.

D'un autre côté, je pense que ce fil a clairement fait des progrès "utilisables". L'une ou l'autre des dernières suggestions (a f b) ou [ a @> f b , avec a <strong i="10">@f</strong> b définissable comme raccourci] est clairement supérieure à mon avis aux suggestions précédentes comme a %f% b ou a |> f <| b .

Pourtant, je pense que d'autres échanges de commentaires ne feront probablement pas de progrès supplémentaires, et j'encourage les gens à utiliser le pouce levé ou le pouce baissé à partir de maintenant, à moins qu'ils n'aient quelque chose de vraiment nouveau à suggérer (c'est-à-dire que n'est pas simplement une modification orthographique d'une proposition existante). J'ai ajouté des émoticônes "hourra" (cône explosif) aux "propositions votables". Si vous pensez que nous ne devrions pas avoir de syntaxe spécialisée pour les fonctions arbitraires en position infixe, alors rejetez le bogue dans son ensemble.

...

ETA : Je pense que cette discussion est maintenant suffisamment mature pour obtenir une balise decision .

Pour référence, (et je m'attendais à ce que quelqu'un d'autre le signale).
Si vous souhaitez intégrer une syntaxe de type SQL, le bon outil pour le travail est les littéraux de chaîne non standard, je pense.
Comme toutes les macros, elles ont accès à toutes les variables de la portée lorsqu'elles sont appelées,
et ils vous permettent de spécifier votre propre DSL, avec votre propre choix de priorité, et ils s'exécutent au moment de la compilation.

select((((:emp_id, :last_name) from employee_tbl) where (:city, == ,"indianapolis")) orderby :emp_id));

Est mieux écrit

sql"SELECT emp_id, last_name FROM employee_tbl WHERE city == 'indianapolis' ORDER BY emp_id"

Les littéraux de chaîne non standard sont une syntaxe très puissante.
Je ne trouve aucun bon exemple d'utilisation pour intégrer un DSL.
Mais ils peuvent le faire.

Et dans ce cas, je pense que le résultat est beaucoup plus propre que n'importe quelle opération infixe qui peut être définie.
Bien qu'il ait la surcharge d'avoir à écrire votre propre microanalyseur/tokenizer.


Je ne vois vraiment pas la nécessité d'une balise de décision.
Cela n'a aucune implémentation en tant que PR, ni aucun prototype utilisable.
qui permet aux gens de le tester.
Contrairement à https://github.com/JuliaLang/julia/issues/5571#issuecomment -205754539 avec ses 8 prototypes utilisables

Mes sentiments envers cela montent et descendent à chaque fois que je lis le fil. Je ne pense pas que je saurai vraiment jusqu'à ce que je l'essaye. Et pour l'instant je ne sais même pas à quoi je l'utiliserais. (Contrairement à certaines des définitions pour |> et <| que j'ai utilisées en F#)

Syntaxe de type SQL, le bon outil pour le travail est les littéraux de chaîne non standard

Que SQL soit mieux fait ou non avec les NSL, je pense qu'il existe un niveau de DSL suffisamment complexe pour que les macros en ligne soient très utiles, mais pas si complexe qu'il vaut la peine d'écrire votre propre microanalyseur/tokenizer.

pour l'instant je ne sais même pas à quoi je l'utiliserais. (Contrairement à certaines des définitions pour |> et <| que j'ai utilisées en F#)

La proposition de macro en ligne permettrait aux utilisateurs, entre autres, de lancer leurs propres macros de type |> ou <| , afin que vous puissiez l'utiliser pour tout ce que vous avez fait en F#.

(Je ne veux pas entrer dans des arguments de va-et-vient de bikeshedding, mais je répondais quand même à cause de ce qui suit, et je pense que la proposition de macro en ligne tue plusieurs oiseaux avec une pierre relativement lisse.)

Je ne vois vraiment pas la nécessité d'une balise de décision.

J'ai demandé plus tôt s'il était approprié pour moi de créer un patch d'analyseur, et personne n'a répondu. Le seul mot à ce sujet jusqu'à présent est:

Je ne pense pas qu'il y aura beaucoup d'« innovation syntaxique » avant Julia 1.0.

Ce qui semblerait s'opposer à la création d'un patch maintenant, car il pourrait simplement rester assis et pourrir. Cependant, vous dites maintenant que cela ne vaut pas la peine de prendre une décision à ce sujet (y compris la décision de ne pas décider maintenant ?) À moins que nous ayons une "implémentation en tant que PR [ou] prototype utilisable".

Qu'est-ce que ça veut dire? (Qu'est-ce qu'un PR ?) Une macro utilisant le caractère '@' au lieu du jeton @ ferait-elle l'affaire, de sorte que <strong i="22">@testinline</strong> a '@'f b => @f(a, b) ? Ou dois-je soumettre un patch à julia-parser.scm ? (J'ai en fait commencé à envisager d'écrire un tel correctif, et il semble que cela devrait être simple, mais mon schéma est très rouillé.) Ai-je besoin de créer des cas de test ?

À l'heure actuelle, il y a 13 participants à ce bug. Il y a un total de 5 personnes qui ont voté sur une ou plusieurs des propositions et/ou ont rejeté le bogue lui-même, et un seul d'entre eux (moi) l'a fait après que la proposition de macro en ligne ait été sur la table. Cela ne me rend pas convaincu qu'il est encore temps pour le prototypage. Lorsque le nombre de personnes qui ont voté depuis la dernière proposition sérieuse sera plus proche de la moitié du nombre de participants, j'espère qu'une sorte de consensus approximatif se dégagera, puis il sera temps de prototyper, de tester et de décider (ou, comme le cas peut être, abandonnant l'idée).

Par « mise en œuvre en tant que PR [ou] prototype utilisable ».
Je veux dire quelque chose avec lequel on peut jouer.
On peut donc voir comment cela se passe dans la pratique.

Un PR est une demande d'extraction, donc un correctif est le terme que vous avez utilisé.

Si vous avez fait un PR, il pourrait être téléchargé et testé.
Plus simplement si vous l'avez implémenté avec des macros
ou Littéraux de chaîne non standard,
il pourrait être testé sans avoir à construire julia.

Comme si ce n'était pas ma décision, mais je doute que je sois incapable de me faire ma propre opinion sans quelque chose avec lequel je peux jouer.

Aussi +1 pour ne pas aller dans les deux sens.

... ou peut-être un package Infix.jl avec des macros et des littéraux de chaîne non standard.

Nous avons définitivement atteint le point "code de travail ou GTFO" dans cette conversation.

OK, voici le code de travail alors : https://github.com/jamesonquinn/JuliaParser.jl

ETA : Dois-je faire référence à un commit spécifique, ou le lien ci-dessus vers le dernier master est-il correct ?

...

(Cela n'a aucune des macros de commodité que je m'attendrais à ce que vous vouliez, comme les équivalents pour |> , <| , ~ et le @defineinfix de mon exemple ci-dessus. Il ne supprime pas non plus _deprecate_ la logique de cas spéciale maintenant inutile pour ~ ou l'opérateur |> . C'est juste les changements de l'analyseur pour le faire fonctionner. J'ai testé les fonctionnalités de base mais pas tous les cas d'angle.

...

Je pense que le vilain hack actuel avec ~ montre qu'il existe un cas d'utilisation clair pour ce genre de chose. En utilisant ce correctif, vous diriez @~ lorsque vous avez besoin d'un comportement de macro ; beaucoup plus propre, sans cas particulier. Ou est-ce que quelqu'un croit sérieusement que ~ est tout à fait unique et que personne ne voudra plus jamais faire ça ?

Notez que le correctif (ce n'est pas encore un PR car il cible l'analyseur amorcé natif, mais pour l'instant le schéma doit venir en premier en termes de PR) est plus généralement utile que le nom du problème ici. Le nom du problème est "opérateurs d'infixes personnalisés" ; le patch donne des macros infixes, les opérateurs infixes n'étant qu'un effet secondaire de cela.

Le patch tel qu'il est n'est pas un changement radical, mais je pense que si cela devenait le plan, la prochaine étape serait de déprécier les ~ et |> actuellement existants, ce qui conduirait éventuellement à changements de rupture.

...

Quelques tests simples ajoutés.

11608 a été clôturé avec un consensus assez clair sur le fait que beaucoup d'entre nous ne veulent pas de macros infixes et que le seul cas actuel d'analyse ~ était une erreur (faite tôt pour la compatibilité R et aucune autre raison particulièrement bonne). Nous avons l'intention de l'abandonner et éventuellement de nous en débarrasser, mais nous ne l'avons pas encore fait (ainsi que le travail de modification de l'API pour l'interface de formule dans les packages JuliaStats).

Les macros sont maintenant techniquement génériques, mais leurs arguments d'entrée sont toujours Expr , Symbol ou des littéraux. Ils ne sont donc pas vraiment extensibles à de nouveaux types définis dans des packages comme le sont les fonctions (infixes ou autres). Les cas d'utilisation possibles pour les macros infixes sont mieux servis par les macros DSL annotées par un préfixe ou les littéraux de chaîne.

(Désolé, j'ai posté prématurément; corrigé maintenant.)

Dans #11608, je vois plusieurs arguments négatifs :

===

En quoi ce qui suit se transformerait-il ?
...
y = 0.0 @in@ x == 1.0 ? 1 @in@ 2 : 3 @in@ 4

Cela a été traité dans le fil :

Des cas comme celui-là sont la raison pour laquelle j'utilise toujours des parenthèses...

et

même précédent ... appliquer sans être des macros : 0.0 in 1 == 1.0 ? 2 in 2 : 3 in 4

===

plus de fonctionnalités à Julia que les gens doivent implémenter, maintenir, tester, apprendre à utiliser, etc.

qui est (partiellement) répondu (et appuyé) ici par :

"Maux de tête pour les développeurs d'analyseurs" est la préoccupation la plus faible possible.

===

n'y a-t-il aucun moyen pour 2 packages d'avoir simultanément des définitions pour le même macro-opérateur qui pourraient être utilisées ensemble sans ambiguïté dans une seule base de code utilisateur?

C'est un point intéressant. Évidemment, si la macro appelle simplement une fonction, alors nous avons toute la puissance de répartition de la fonction. Mais s'il s'agit d'une vraie macro, comme avec ~ , alors c'est plus compliqué. Oui, vous pouvez imaginer des solutions de contournement hackish, comme essayer de l'appeler en tant que fonction, et attraper toutes les erreurs pour l'utiliser comme macro... mais ce genre de laideur ne devrait pas être encouragé.

Pourtant, c'est tout autant un problème pour n'importe quelle macro. Si deux packages exportent tous les deux une macro, vous ne pouvez tout simplement pas avoir les deux avec "using".

Est-ce que cela risque d'être plus un problème avec les macros infixes ? Eh bien, cela dépend de la raison pour laquelle les gens finissent par les utiliser :

  • Juste un moyen d'avoir des fonctions infixes définies par l'utilisateur. Dans ce cas, elles ne sont pas pires que n'importe quelle autre fonction ; l'expédition fonctionne bien.
  • Pour utiliser d'autres styles de programmation, utilisez des opérateurs tels que |> et <| dont @Glen-O discute ci-dessus. Dans ce cas, je pense qu'il se développera rapidement des conventions communes sur ce que macro signifie quoi, avec peu de chance de collision.
  • Comme moyen de créer des DSL à usage spécial, comme l'exemple SQL ci-dessus. Je pense que ceux-ci seront utilisés dans des contextes spécifiques et que le risque de collision n'est pas trop grave.
  • Pour des choses comme R's ~ . Au début, cela semble le plus problématique; dans R, ~ est utilisé pour plusieurs choses différentes. Cependant, je pense que même là, c'est gérable, avec quelque chose comme :

macro ~(a,b) :(~(:$a, quote($b))) end

Ensuite, la fonction ~ pourrait être distribuée en fonction du type du LHS, mais le RHS serait toujours un Expr. Ce genre de chose permettrait aux principales utilisations qu'il a dans R (régression et graphique) de coexister, c'est-à-dire de se répartir correctement malgré la provenance de packages différents.

(Remarque : ce qui précède a été modifié. Initialement, je pensais qu'une expression R telle que a ~ b + c utilisait la liaison de b et c via l'évaluation paresseuse de R. Mais ce n'est pas le cas. t ; b et c sont les noms des colonnes dans un bloc de données transmis explicitement, et non les noms des variables dans la portée locale qui sont ainsi transmises implicitement.)

===

La seule façon d'avancer ici serait de développer une mise en œuvre réelle.

Ce que j'ai fait.

===

Les macros sont désormais techniquement génériques, mais leurs arguments d'entrée sont toujours Expr, Symbol ou des littéraux. Ils ne sont donc pas vraiment extensibles à de nouveaux types définis dans des packages comme le sont les fonctions (infixes ou autres).

Cela se rapporte au point ci-dessus. Dans la mesure où une macro infixe appelle une fonction spécifique, cette fonction est toujours extensible via dispatch de la manière normale. Dans la mesure où il n'appelle pas une fonction spécifique, il fait quelque chose de structurel/syntaxique (comme ce que |> fait maintenant) qui ne devrait pas être étendu ou redéfini. Notez que même s'il appelle une fonction, le fait qu'il s'agisse d'une macro peut toujours être utile ; par exemple, il peut citer certains de ses arguments, ou les transformer en rappels, ou même interagir simultanément avec le nom et la liaison d'une variable, d'une manière qu'un appel direct de fonction ne peut pas.

===

Les cas d'utilisation possibles pour les macros infixes sont mieux servis par les macros DSL annotées par un préfixe ou les littéraux de chaîne.

Comme indiqué dans le fil référencé:

[L'infixe est] plus facile à analyser (pour l'anglais et la plupart des locuteurs occidentaux), car notre langue fonctionne de cette façon. (La même chose vaut généralement pour les opérateurs.)

Par exemple, qui est plus lisible (et inscriptible):

select((:emp_id, :last_name) <strong i="8">@from</strong> employee_tbl <strong i="9">@where</strong> city == 'NYC' <strong i="10">@orderby</strong> :emp_id)

ou

send(orderby((<strong i="14">@where</strong> selectfrom((:emp_id, :last_name), employee_tbl) city == 'NYC'), :emp_id))

?

===

Enfin:

11608 a été clôturé avec un consensus assez clair

Cela me semble assez partagé, avec "qui va faire le travail" qui a le vote décisif. Ce qui est maintenant au moins en partie sans objet ; J'ai fait le travail dans JuliaParser et je serais prêt à le faire dans Scheme si les gens aiment cette idée.

Ceci est mon dernier message dans ce fil, à moins qu'il y ait une réaction positive à mon juliaparser piraté. Ce n'est pas mon intention d'imposer ma volonté ; juste pour présenter mon point de vue.

Je plaide en faveur des macros infixes ( a <strong i="6">@m</strong> b => <strong i="8">@m</strong> a b ). Cela ne veut pas dire que je ne connais pas les arguments contre. Voici comment je résumerais le meilleur argument contre :

Les fonctions linguistiques commencent à -100. Qu'offrent les macros infixes qui pourraient éventuellement surmonter cela? De par leur nature même, il n'y a rien que vous puissiez accomplir avec des macros d'infixe qui ne puisse pas être accompli avec des macros de préfixe.

Ma réponse est : Julia est avant tout un langage pour les programmeurs STEM. Mathématiciens, ingénieurs, statisticiens, physiciens, biologistes, spécialistes de l'apprentissage automatique, chimistes, économètres... Et une chose que je pense que la plupart de ces gens réalisent est l'utilité d'une bonne notation. Pour prendre un exemple que je connais bien en statistique : ajouter des variables aléatoires indépendantes équivaut à convoluer des PDF, ou même à convoluer des dérivés de CDF, mais souvent exprimer quelque chose en utilisant la première peut être d'un ordre de grandeur plus concis et compréhensible que la seconde .

Infixe contre préfixe contre postfixe est, dans une certaine mesure, une question de goût. Mais il existe également des raisons objectives de préférer l'infixe dans de nombreux cas. Alors que le préfixe et le postfixe conduisent à des précipités indigestes d'opérateurs consécutifs comme ceux qui font ressembler les programmeurs Forth à des politiciens allemands, ou ceux qui font ressembler les programmeurs Lisp à une caricature chomskienne, l'infixe place les opérateurs dans ce qui est souvent le plus cognitif. place naturelle, aussi près que possible de tous leurs opérandes. Il y a une raison pour laquelle personne n'écrit d'articles de mathématiques dans Forth, et pourquoi même les mathématiciens allemands utilisent des opérateurs infixes lors de l'écriture d'équations.

Oui, les macros infixes peuvent être utilisées pour écrire du code obscurci. Mais les macros de préfixe existantes sont tout aussi sujettes aux abus. Si elles ne sont pas utilisées de manière abusive, les macros infixes peuvent conduire à un code beaucoup plus clair .

  • (a+b <strong i="18">@choose</strong> b) bat binomial(a+b,b) ;
  • score ~ age + treatment bat linearDependency(:score, :(age + treatment)) ;
  • domSelect("#logo") @| css "color" "red" @| fadeIn "slow" <strong i="25">@thenApply</strong> addClass "dummy" bat l'enfer sur addOneTimeEventListener(fadeIn(css(domSelect("#logo"),"color","red"),"slow"),"done",(obj,evt)->addClass(obj,"dummy")) .

Je me rends compte que ce ne sont que des exemples de jouets, mais je pense que le principe est valable.

Ce qui précède pourrait-il être fait avec des littéraux de chaîne non standard ? Eh bien, les deuxième et troisième exemples fonctionneraient comme des NSL. Mais le problème avec les NSL est qu'elles vous donnent trop de liberté : à moins que vous ne soyez familier avec la grammaire particulière, il n'y a aucun moyen d'être sûr même de ce que sont les jetons d'une NSL, sans parler de son ordre d'opérations. Avec les macros infixes, vous avez suffisamment de liberté pour faire tous les exemples ci-dessus, mais pas tellement qu'il n'est pas clair à la lecture du "bon" code ce que sont les jetons et où vont les parenthèses implicites.

Il a besoin que certaines choses soient déplacées d'inconnues inconnues vers des inconnues connues. Et malheureusement, il n'y a pas de mécanisme pour le faire. Vos arguments ont besoin d'une structure qui n'existe pas.

Maintenant que <| est associatif à droite (#24153), la proposition initiale a |>op<| b fonctionne-t-elle ?

J'ai créé un package pour le hack mentionné par Steven dans https://github.com/JuliaLang/julia/pull/24404#issuecomment -341570934 :

Je ne sais pas combien d'opérateurs infixes potentiels cela affecte, mais j'aimerais vraiment utiliser <~ . L'analyseur ne coopérera pas - même si j'espace soigneusement les choses, il veut que a <~ b signifie a < (~b) .

<- a un problème similaire.

Désolé si cela est déjà couvert par ce problème ou un autre, mais je ne l'ai pas trouvé.

Nous pourrions potentiellement exiger des espaces dans a < ~b ; nous avons déjà ajouté des règles comme celle-là. Ensuite, nous pourrions ajouter <- et <~ comme opérateurs infixes.

Merci @JeffBezanson , ce serait génial ! S'agirait-il d'un cas particulier ou d'une règle plus générale ? Je suis sûr qu'il y a quelques détails dans ce que la règle devrait être pour autoriser plus d'opérateurs infixes, donner un code clair et prévisible et casser le moins possible le code existant. Quoi qu'il en soit, j'apprécie l'aide et la réponse rapide. Bonne année!

Dans le cas où a <~ b serait différent de a < ~b , j'aimerais voir a =+ 1 comme erreur (ou avertissement au moins)

Je sais que c'est une discussion assez ancienne, et la question posée a été posée il y a un certain temps, mais j'ai pensé qu'il valait la peine d'y répondre:

Maintenant que <| est associatif à droite (#24153), la proposition initiale a |>op<| b fonctionne-t-elle ?

Non, malheureusement, |> a toujours la priorité. La mise à jour effectuée fait en sorte que, si vous définissez <|(a,b)=a(b) , vous pouvez réussir à faire a<|b<|c pour obtenir a(b(c)) ... mais c'est un concept différent.

Frozen pendant 2 ans, un commentaire et un commit il y a 2 et 5 jours !

Voir Documenter les opérateurs binaires personnalisables f45b6be

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

Questions connexes

sbromberger picture sbromberger  ·  3Commentaires

StefanKarpinski picture StefanKarpinski  ·  3Commentaires

yurivish picture yurivish  ·  3Commentaires

helgee picture helgee  ·  3Commentaires

iamed2 picture iamed2  ·  3Commentaires