1 points par GN⁺ 2024-11-14 | 1 commentaires | Partager sur WhatsApp
  • Raku ressemble davantage à un laboratoire de fonctionnalités de langage qu’à un langage poli autour d’une conception unifiée et aboutie, ce qui permet d’observer en un seul endroit des expérimentations que d’autres langages pourraient adopter
  • Les Junctions propagent les opérations à travers un ensemble de valeurs possibles, puis les réduisent via des quantificateurs booléens comme all, any ou one, ce qui permet d’exprimer brièvement des conditions complexes
  • Le * des Whatevers sert aux plages infinies, aux fonctions anonymes et à l’indexation fondée sur la longueur des listes, produisant un style de code concis mais déroutant
  • Les expressions régulières de Raku abandonnent la compatibilité PCRE au profit de grammaires composables, de séparateurs de valeurs, de lookahead plus lisibles et de l’ignorance des espaces par défaut
  • Entre les hyperoperators, la syntaxe Pair, les slangs, RakuAST et Rakudo Star, Raku est un langage qui expérimente largement, de la syntaxe jusqu’au mode de distribution

Pourquoi Raku ressemble à un laboratoire de fonctionnalités de langage

  • Après avoir utilisé Raku pendant plus d’un an, il apparaît davantage comme un « laboratoire pour les fonctionnalités de langage »
  • Ses cinq modèles de concurrence et ses multiples manières de faire une même chose révèlent un caractère expérimental, destiné à « voir ce qui se passe »
  • Si beaucoup de fonctionnalités interagissent de façon étrange et génèrent de nombreux cas limites, c’est aussi parce qu’exécuter 100 expériences est bien plus simple que les affiner pour qu’elles fonctionnent ensemble
  • Les fonctionnalités abordées ici sont des expérimentations à la manière de Raku, qu’on pourrait un jour retrouver dans d’autres langages de programmation

Junctions : superposition de valeurs possibles et quantificateurs booléens

  • Les Junctions représentent une superposition de valeurs possibles ; lorsqu’on applique une opération à une junction, cette même opération s’applique à toutes les valeurs qu’elle contient
    • 2|10 devient any(2, 10)
    • 2&10 + 3 devient all(5, 13)
    • (1&2) + (10^20) se combine en quelque chose comme all(one(11, 21), one(12, 22))
  • L’usage principal des Junctions est d’exprimer des formules booléennes comme all et any
    • il n’existe pas de moyen de déstructurer une junction
    • pour s’en servir réellement, il faut d’abord la réduire à un booléen
  • so force la conversion d’une junction en booléen
    • so (1&2) + (10^20) < 15 donne True
    • so (1&2) + (10^20) > 0 donne False
  • En Raku, list.all n’est pas une méthode qui prend des arguments, mais une méthode sans argument qui transforme une liste en junction
    • <1 2 3>.all devient all(1, 2, 3)
    • si on applique is-prime($x), cela s’applique à chaque valeur, comme dans all(False, True, True)
  • En combinant des Junctions, on peut aussi produire un comportement de type unique
    • « chaque élément est-il égal à exactement un élément ? » peut s’exprimer par {.all == .one}
    • <1 2 3 7> est vrai, tandis que <1 2 3 7 2> est faux

Whatevers : plages infinies et fonctions anonymes avec *

  • Le * des Whatevers joue plusieurs rôles dans Raku ; c’est le symbole whatever
  • Dans les plages ou les séquences, * signifie qu’il n’y a pas de borne
    • 1..* équivaut à 1..Inf
    • (2,4,8...*)[17] vaut 262144
  • Une expression qui contient * est promue en fonction anonyme
    • ce comportement s’appelle le whatever-priming
    • le résultat est un WhateverCode et, hormis son type, il ne se distingue pas d’une autre fonction
    • (* + 10)(2) donne 12
    • (^10).map(* % 2) produit un motif 0 1 0 1 ...
  • Si une expression contient deux *, chacun devient un argument positionnel distinct
    • (2, 30, 4, 50).map(* + *) renvoie (32, 54)
    • ce comportement est utile pour certaines définitions délicates de Fibonacci, même s’il n’est pas évident qu’il soit préférable à une sémantique où chaque * représenterait la même valeur
  • De nombreuses méthodes de Raku peuvent recevoir une fonction, d’où l’usage fréquent du priming
    • l[*-1] ressemble à un index négatif, mais en réalité [] transmet la longueur de la liste à une fonction qui calcule l’index à consulter
    • si la liste a une longueur de 10, alors l[*-1] devient l[10-1], donc le dernier élément
    • l.head(2) désigne les deux premiers éléments, tandis que l.head(*-2) signifie tout sauf les deux derniers
  • En passant une autre fonction à [], on peut aussi implémenter simplement des structures comme un ring buffer
    • @x[95 % *]-- calcule sa position à partir de la longueur de la liste

Expressions régulières : des grammaires composables plutôt que la compatibilité PCRE

  • Raku utilise une nouvelle syntaxe d’expressions régulières, différente des regex POSIX ou de PCRE
    • les regex POSIX reposent sur une structure chargée en antislashs et en ponctuation
    • PCRE reste rétrocompatible avec POSIX et en reprend la complexité faite d’antislashs et de ponctuation
    • Perl 6 a rompu cette rétrocompatibilité pour introduire une nouvelle syntaxe de regex
  • L’amélioration la plus marquante est la composabilité
    • dans beaucoup de langages, on compose les regex en concaténant des chaînes représentant des motifs
    • Raku permet d’insérer une autre regex comme dans /< foo >+/, afin de faire correspondre une ou plusieurs occurrences de la regex foo sans qu’elle ne « fuite » dans la regex englobante
  • Cela permet de découper des regex complexes en éléments plus petits et plus lisibles
  • En rompant la rétrocompatibilité, plusieurs fonctionnalités de confort ont aussi été ajoutées
    • \d+ % ',' correspond à 1, 1,2 et 1,1,4, mais pas à 1, ni à 12
    • les lookahead et les groupes non capturants n’utilisent pas de combinaisons de symboles obscures
    • r1 && r2 ne correspond qu’aux chaînes qui satisfont à la fois r1 et r2
    • : permet d’arrêter le backtracking
    • les espaces sont ignorés par défaut et doivent être réactivés explicitement dans les motifs
  • Indépendamment de ses capacités d’exécution, la notation des regex de Raku peut être vue comme un ensemble d’idées susceptibles d’apparaître dans d’autres langages

Hyperoperators : un mapping généralisé qui descend dans les listes imbriquées

  • La forme de base des Hyperoperators, l>>.method, ressemble à map, mais descend récursivement dans les sous-listes
    • [1, [2, 3], 4]>>.succ donne [2 [3 4] 5]
  • En Raku, un appel de fonction de la forme f(list, *args) peut être transformé en une forme méthode list.&f(* args)
    • c’est pourquoi >>. fonctionne comme un opérateur de mapping généralisé
    • combiné avec Whatever, on peut écrire [1, [2, 3], 4]>>.&(*+1)
  • L’hyperoperator binaire l1 << op >> l2 applique op élément par élément aux deux listes
    • la liste la plus courte est répétée jusqu’à ce que la plus longue se termine
    • [1, 2, 3, 4, 5] <<+>> [10, 20] donne [11 22 13 24 15]
    • les formes << op<< ou >>op>> ne répètent que jusqu’à la fin de la liste de gauche ou de droite
  • Cela fonctionne aussi avec une valeur unique et avec des listes imbriquées
    • [1, 2, 3, 4, 5] <<+>> 10 donne [11 12 13 14 15]
    • [1, [2, 3], 4, 5] <<+>> [10, 20] donne [11 [22 23] 14 25]
  • Appliqué à deux hash, l’hyperoperator a aussi un comportement distinct qui applique op à l’union, à l’intersection ou à la différence des hash
  • Le nom d’hyperoperator vient du fait que toutes les opérations sont évaluées en parallèle
    • même une boucle explicite peut être parallélisée via le préfixe hyper

Syntaxe Pair : l’unité de base des hash et un passage de drapeaux concis

  • Les Pair sont l’unité de base des hash en Raku
    • un hash est une collection de paires key => value
    • la pair est le type fondamental, et le hash est une collection de pairs
  • Raku propose de nombreuses syntaxes abrégées à base de deux-points pour définir une pair
    • my $x = 3; :$x signifie x => 3
    • :a<$x> signifie a => "$x"
    • :a($x) signifie a => 3
    • :3a signifie a => 3
  • L’abréviation la plus importante est :key et :!key
    • elles correspondent respectivement à key => True et key => False
    • elles servent de forme concise pour passer des drapeaux à une méthode
  • La méthode match accepte plusieurs colon pairs utilisées comme drapeaux
    • on y trouve des formes comme :continue(:$c), :pos(:$p), :global(:$g), :overlap(:$ov)
    • dans une signature, :f(:$foo) définit le paramètre $foo tout en créant l’alias :f
    • l’appelant peut alors activer le même drapeau avec :f ou :foo
  • Les arguments en colon pair sont souples en ordre et en position
    • "abab".match(/../, :g) effectue une correspondance globale
    • "abab".match(/../, :g, :ov) effectue une correspondance avec chevauchement
    • on peut inverser l’ordre et les coller, comme dans "abab".match(:g:ov, /../)
    • on peut même placer des arguments nommés après l’appel de fonction, comme dans "abab".match(:g, /../):ov:2nd
  • Cette syntaxe permet d’indiquer des options de méthode bien plus brièvement que match(global=True, overlap=True)

Slangs, RakuAST, Rakudo Star

  • Les Slangs permettent de modifier la grammaire de Raku
  • RakuAST permet d’analyser des expressions Raku en AST depuis l’intérieur même de Raku
    • Q/my $x; $x++/.AST affiche une déclaration de variable et un incrément postfixé sous forme de RakuAST::StatementList
    • un exemple montre aussi comment produire du code Raku avec une notation différente, comme Q/my $x; put $x/.AST.DEPARSE("NL")
  • Raku dispose d’une distribution installable appelée Rakudo Star
    • cette distribution fournit à l’avance une sélection de modules tiers
    • elle constitue un compromis entre la charge de maintenance d’une grande bibliothèque standard et la nécessité, pour l’utilisateur, d’aller lui-même trouver les bons paquets dans l’écosystème

1 commentaires

 
GN⁺ 2024-11-14
Avis sur Hacker News
  • J’ai autrefois implémenté indépendamment, pour JavaScript, quelque chose de similaire à la fonctionnalité de regex compositionnelles évoquée ici, et même des années plus tard, c’est l’une des bibliothèques que je réutilise le plus souvent dans d’autres projets.
    On se rapproche un peu plus de l’impression d’avoir des parseurs de première classe dans le langage ; par exemple, je m’en suis servi pour implémenter le parsing de types de médias avec des regex : https://runkit.com/tolmasky/media-type-parsing-with-template...
    Sur npm : templated-regular-expression, GitHub : https://github.com/tolmasky/templated-regular-expression
    Bien sûr, un langage de programmation devrait avoir de vrais parseurs et on ne devrait pas utiliser les regex comme parseurs, mais puisqu’on va de toute façon utiliser des regex, c’est vraiment appréciable de pouvoir les découper en petits morceaux.

    • Un « vrai parseur » n’est pas assez puissant pour parser Raku.
      Les regex de Raku sont bien plus puissantes lorsqu’elles sont combinées à une grammar, et bien écrites, elles sont plus faciles à comprendre que n’importe quel « vrai parseur ». Pour parser Raku avec un parseur ordinaire, il faudrait pouvoir ajouter et retirer des éléments au parseur pendant le parsing ; le parseur de Raku gère cela en sous-classant la grammar courante afin d’ajouter ou de retirer des éléments dans la portée lexicale actuelle, puis de revenir à la grammar précédente lorsque la portée se termine.
      En Raku, les regex sont une autre syntaxe pour écrire du code ; elles ne diffèrent de la syntaxe de base que par quelques règles et comportements, et peuvent avoir des paramètres et des variables. Si la syntaxe regex ne convient pas à une tâche, on peut insérer de la syntaxe Raku ordinaire pour faire ce qu’il faut, puis revenir à la syntaxe regex.
      Par exemple, il est possible de faire correspondre un ou plusieurs A, suivis du même nombre de B et du même nombre de C. Dans la plupart des syntaxes de regex, faire correspondre le même nombre de A et de B nécessite une récursion intermédiaire, mais cela rend difficile de traiter aussi les C de la même manière, et c’est beaucoup plus pénible à lire. L’approche de Raku n’a pas besoin de backtracking ni de récursion, et peut donc s’exécuter assez rapidement.
      En les regroupant dans une grammar, on obtient un arbre de parsing complet ; c’est possible sans grammar, mais l’utiliser est plus simple. Comme exemple de parseur réel, je recommande souvent JSON::TINY::Grammar : https://github.com/moritz/json/blob/master/lib/JSON/Tiny/Gra...
      Honnêtement, une bonne partie de la conception des « vrais parseurs » me semble être un sous-produit des limites de RAM des premiers ordinateurs. L’étape de tokenisation séparée servait à réduire la RAM occupée par le code source afin de garder de la mémoire pour l’analyse sémantique et la compilation ultérieures, et je ne pense pas qu’elle simplifie tant que ça ces étapes suivantes. Dans le module JSON::Tiny ci-dessus, la grammar construit des structures de données Raku natives via une classe d’actions pendant le parsing ; en pratique, elle compile donc en même temps qu’elle parse.
  • J’utilise Raku en production. Pour le traitement de texte, c’est le meilleur langage, et l’expérience de création de parseurs est excellente.
    Je suis étonné que ce ne soit pas un langage de premier plan pour construire des pipelines de texte pour LLM.

    • J’arrive un peu tard, mais je me demande si quelqu’un connaît un bon exemple en Raku pour appeler une API HTTPS et faire du polling jusqu’à ce qu’une certaine valeur soit renvoyée.
    • Je me demande si vous utilisez des packages LLM pour Raku, et si oui lesquels.
  • Waouh. Ça me donne envie de quitter le secteur avant le jour où je devrai maintenir une base de code Raku.

    • Amusant : après avoir lu ce billet de blog, j’ai eu envie de quitter mon boulot et d’aller trouver la team Raku pour travailler avec eux. Je suis peut-être encore trop naïf :)
    • Si vous n’avez jamais vu de code Raku ordinaire, ce texte peut tout à fait susciter ce genre de réaction.
      Quand on lit des livres d’introduction à Raku, beaucoup de choses donnent l’impression d’être du Python avec une syntaxe de la famille C. La syntaxe est centrée sur les accolades, mais la facilité d’utilisation, les structures de données intégrées et les fonctionnalités orientées objet sont toutes de haut niveau.
      Si vous connaissez d’autres langages de script de haut niveau, le code Raku de scripts similaires devrait être assez facile à lire. Je ne pense pas que la plupart des gens utilisent les fonctionnalités vraiment étranges dans le code quotidien ; Raku est plus flexible, mais le code ordinaire ne paraît généralement pas obscur.
    • C’est un peu comme Perl. Personne n’a envie d’en assurer la maintenance, mais l’utiliser est vraiment amusant. Son expressivité est très élevée.
      On le voit aussi dans la possibilité, en Raku, de définir des arguments nommés sous forme abrégée, par exemple :global(:$g), ou d’appeler une correspondance regex globale comme match(/foo/, :g) en supposant la valeur True. Perl a aussi beaucoup de choses de ce genre, toutes faites pour écrire plus vite et de façon plus agréable, mais moins lisibles pour les débutants.
    • C’est étrange que les gens disent des choses similaires à propos de la maintenance de bases de code écrites avec l’aide de l’IA.
      Cela risque maintenant de devenir une fracture générationnelle. Toute une génération de programmeurs plus âgés pourrait avoir l’impression d’être dépassée dans un environnement de travail que la génération actuelle trouve normal.
    • Je ne pense pas que Raku soit un langage pensé pour « l’industrie ».
  • Certaines fonctionnalités me semblent à moitié familières. Hyper ressemble à une version plus improvisée de quelque chose du côté des recursion-schemes, et le * montré ici est assez proche du _ de Scala.
    J’aime bien utiliser _ dans les lambdas, et j’aimerais que tous les langages adoptent quelque chose de similaire.

  • > (2, 30, 4, 50).map(* + *) returns (32, 45) ne devrait-il pas plutôt être returns (32, 54) ? Le deuxième terme serait 4+50.
    C’est peut-être le résultat d’une traduction littérale introductive, venue de pays où l’on dit « quatre et cinquante », comme vierenvijftig, au lieu de fifty-four en anglais.

    • J’ai vérifié avec rakudo, et en réalité cela renvoie bien (32 54). L’auteur a sans doute fait une faute de frappe.
  • Alors Perl est maintenant une drogue d’initiation aux langages de la famille APL ?

    • Oui. Et en plus, l’article ne traite même pas des méta-opérateurs.
      Par exemple, on peut utiliser le méta-opérateur de réduction [ ] avec l’infixe + pour « tout additionner » : [+] 1, 2, 3
  • Raku a toujours un gros problème de vitesse. Parser des fichiers de logs avec des expressions régulières, c’est le point fort de Perl, mais même le Raku actuel prend 6,5 fois plus de temps que Python 3.13, hors temps de démarrage

    • Il faudrait donner un exemple avec. D’après mon expérience, il y a des cas où Raku est plus rapide et d’autres où il est plus lent ; donc dire simplement « Raku prend 6,5 fois plus de temps que Python 3.13 » sans voir sur quoi il est lent n’a pas grand sens
  • J’ai été vraiment surpris que (2,4,8...*)[17] donne 262144

    • Le mot qui m’est immédiatement venu à l’esprit, c’est « horrible », puis j’ai pensé à « résultat horrible » et au problème de l’arrêt, mais chacun ses goûts
    • La détection de l’incrément existe depuis longtemps dans Perl6, ce n’est donc pas une nouveauté.
      À part Whatever, la nouveauté depuis Perl6/Raku me semble plutôt du côté de l’évaluation paresseuse
    • C’est assez cohérent.
      (1,3,9...*)[4,5] donne (81 243), et (1,3,9...*)[(1..3)] donne (3 9 27).
      On peut aussi imbriquer, avec des expressions comme (1,2,4...*)[(1,2,4...*)[1,2,3]]
  • L’article a sélectionné quelques fonctionnalités intéressantes de Raku.
    À mon avis, les fonctionnalités les plus importantes de Raku sont l’interface en ligne de commande et les grammaires.
    La prise en charge de l’interface en ligne de commande est une fonctionnalité courante ; il existe par exemple des implémentations et des cas d’adoption de docopt. Mais dans Raku, cette fonctionnalité est intégrée et agréable à utiliser.
    Les grammaires, en revanche, sont rares en ceci que le langage de programmation les fournit comme éléments de première classe, et permet de les créer et de les composer avec la programmation orientée objet

  • Je suis ce projet depuis des années et il est intéressant, mais je trouve vraiment dommage que Perl 6 se soit autant perdu dans ce voyage au pays des miroirs

    • Je me demande ce que fait Patrick Michaud ces temps-ci, et comment il va