- 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,anyouone, 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|10devientany(2, 10)2&10 + 3devientall(5, 13)(1&2) + (10^20)se combine en quelque chose commeall(one(11, 21), one(12, 22))
- L’usage principal des Junctions est d’exprimer des formules booléennes comme
alletany- 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
soforce la conversion d’une junction en booléenso (1&2) + (10^20) < 15donneTrueso (1&2) + (10^20) > 0donneFalse
- En Raku,
list.alln’est pas une méthode qui prend des arguments, mais une méthode sans argument qui transforme une liste en junction<1 2 3>.alldevientall(1, 2, 3)- si on applique
is-prime($x), cela s’applique à chaque valeur, comme dansall(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
- « chaque élément est-il égal à exactement un élément ? » peut s’exprimer par
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 borne1..*équivaut à1..Inf(2,4,8...*)[17]vaut262144
- Une expression qui contient
*est promue en fonction anonyme- ce comportement s’appelle le whatever-priming
- le résultat est un
WhateverCodeet, hormis son type, il ne se distingue pas d’une autre fonction (* + 10)(2)donne12(^10).map(* % 2)produit un motif0 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]devientl[10-1], donc le dernier élément l.head(2)désigne les deux premiers éléments, tandis quel.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 regexfoosans qu’elle ne « fuite » dans la regex englobante
- Cela permet de découper des regex complexes en éléments plus petits et plus lisibles
- c’est aussi ce qui sert de base au support intégré des grammaires d’analyse dans Raku
- En rompant la rétrocompatibilité, plusieurs fonctionnalités de confort ont aussi été ajoutées
\d+ % ','correspond à1,1,2et1,1,4, mais pas à1,ni à12- les lookahead et les groupes non capturants n’utilisent pas de combinaisons de symboles obscures
r1 && r2ne correspond qu’aux chaînes qui satisfont à la foisr1etr2: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]>>.succdonne[2 [3 4] 5]
- En Raku, un appel de fonction de la forme
f(list, *args)peut être transformé en une forme méthodelist.&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)
- c’est pourquoi
- L’hyperoperator binaire
l1 << op >> l2appliqueopé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] <<+>> 10donne[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
- même une boucle explicite peut être parallélisée via le préfixe
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
- un hash est une collection de paires
- Raku propose de nombreuses syntaxes abrégées à base de deux-points pour définir une pair
my $x = 3; :$xsignifiex => 3:a<$x>signifiea => "$x":a($x)signifiea => 3:3asignifiea => 3
- L’abréviation la plus importante est
:keyet:!key- elles correspondent respectivement à
key => Trueetkey => False - elles servent de forme concise pour passer des drapeaux à une méthode
- elles correspondent respectivement à
- La méthode
matchaccepte 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$footout en créant l’alias:f - l’appelant peut alors activer le même drapeau avec
:fou:foo
- on y trouve des formes comme
- 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
- ils peuvent servir à modifier la syntaxe des boucles, changer les identifiants, ajouter des actors ou intégrer des séquences ADN
- parmi les exemples cités figurent Slang::Otherwise, Slang::Piersing, OO::Actors et BioInfo
- RakuAST permet d’analyser des expressions Raku en AST depuis l’intérieur même de Raku
Q/my $x; $x++/.ASTaffiche une déclaration de variable et un incrément postfixé sous forme deRakuAST::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
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-expressionBien 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.
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 deBet du même nombre deC. Dans la plupart des syntaxes de regex, faire correspondre le même nombre deAet deBnécessite une récursion intermédiaire, mais cela rend difficile de traiter aussi lesCde 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.
Waouh. Ça me donne envie de quitter le secteur avant le jour où je devrai maintenir une base de code Raku.
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.
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 commematch(/foo/, :g)en supposant la valeurTrue. 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.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.
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 êtrereturns (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.
(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 ?
Par exemple, on peut utiliser le méta-opérateur de réduction
[ ]avec l’infixe+pour « tout additionner » :[+] 1, 2, 3Raku 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
J’ai été vraiment surpris que
(2,4,8...*)[17]donne262144À part
Whatever, la nouveauté depuis Perl6/Raku me semble plutôt du côté de l’évaluation paresseuse(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