1. Prélude▲
Après une longue période de gestation, la première version stable « de production » du nouveau langage Perl 6 a été officiellement annoncée en décembre 2015 et est disponible au téléchargement. La distribution stable complète s'appelle Rakudo Star et contient Rakudo Perl 6, le compilateur Perl 6, la machine virtuelle MoarVM, Panda, un utilitaire d'installation de modules, une suite de modules additionnels et une abondante documentation. Cette distribution est disponible pour les environnements Linux, Mac OS X et Windows (voir encadré).
Perl 6 est une nouvelle mouture du langage Perl, qui reste dans l'esprit des versions antérieures de Perl (« fais ce que je veux », « il y a plus d'une façon de le faire », etc.), mais les concepteurs de Perl 6 ont décidé de rompre avec la tradition de compatibilité ascendante de la syntaxe remontant à plus de 25 ans, ce qui a permis un toilettage assez important de la syntaxe et ouvert des possibilités réellement nouvelles et radicalement modernes.
Installer et utiliser Rakudo / Perl 6
Sous Windows, le plus simple est d'installer la distribution MSI disponible sous ce lien : http://rakudo.org/downloads/star/rakudo-star-latest-x86_64%20(JIT).msi. Il n'y a rien de plus à faire pour que ça fonctionne.
De même, sous Mac OS X, télécharger et installer la distribution dmg : http://rakudo.org/downloads/star/rakudo-star-latest.dmg.
À l'heure où nous écrivons, les paquets proposés par les grandes distributions Linux proposent souvent des versions trop anciennes. Vérifiez cependant pour votre propre distribution, car la situation évolue assez rapidement, mais, par exemple, Debian propose un paquet « stable » datant de 2014 et un paquet de test d'octobre 2016. Idéalement, il faudrait au moins la version 2016.01 de Rakudo-Star pour avoir la version stable de production de Perl 6.
À défaut d'une distribution packagée stable, il est toujours possible de télécharger le fichier tar à l'adresse http://rakudo.org/downloads/star/ et de l'installer soi-même. Par exemple :
$ wget http://rakudo.org/downloads/star/rakudo-star-2016.01.tar.gz
$ tar xzf rakudo-star-2016.01.tar.gz
$ cd rakudo-star-2016.01
$ perl Configure.pl --backend=moar --gen-moar
$ make
$ make rakudo-test
$ make rakudo-spectest
$ make install
Des informations complémentaires peuvent être trouvées sur le site de Rakudo : http://rakudo.org/how-to-get-rakudo/.
Une fois l'installation effectuée, vous voudrez sans doute mettre à jour votre PATH dans le fichier ~/.bashrc puis sourcer ce fichier. Ensuite, en tapant perl6 -v à la ligne de commande, vous devriez avoir quelque chose du genre :
$ perl6 -v
This is Rakudo version 2016.01.1 built on MoarVM version 2016.01
implementing Perl 6.c.
Sous Docker, obtenez l'image officielle docker pull rakudo-star, puis exécutez docker run -it rakudo-star.
En tapant perl6 à l'invite du shell Linux ou de la fenêtre cmd de Windows, on entre dans la boucle REPL (read, evaluate, print loop) qui affiche une invite de ligne de commande permettant de tester des instructions ou expressions Perl 6 simples.
>
say
"Hello World!"
;
Hello World!
Pour exécuter un script, tapez simplement perl6 suivi du nom du programme :
perl6 script.pl6
La documentation officielle (en anglais) sur Perl 6 est disponible à l'adresse suivante : doc.perl6.org.
2. Brève introduction à Perl 6▲
Prenez un langage de programmation généraliste quelconque et affichez le résultat de la simple opération arithmétique suivante : 0.3 - 0.2 - 0.1. Il y a toutes les chances que vous obteniez un résultat tel que -2.77555756156289e-17 (en Perl 5), -2.775558e-17 (en C sous gcc) ou -2.7755575615628914e-17 (en Java, Python 3, Ruby ou TCL). Ce dernier résultat paraît peut-être plus précis (plus de décimales), mais il se trouve en fait être légèrement plus loin de la vérité, puisque ce total devrait normalement être égal à 0. Ces résultats erronés ne sont pas dus à une faiblesse des langages en question, mais au fait que nos ordinateurs ne sont pas très bons en arithmétique avec des nombres fractionnaires.
Essayons avec la boucle REPL de Perl 6 :
>
my $l
'addition-devrait-être-zéro = .3 - .2 - .1;
0
> say sprintf "%.50f", $l'
addition-
devrait-
être-
zéro;
0
.00000000000000000000000000000000000000000000000000
On voit que Perl 6 affiche la valeur correcte (0), même quand on demande d'afficher les 50 premières décimales. Ce résultat est correct parce qu'en interne, Perl 6 stocke des nombres décimaux dans un type Rat (rationnel), sous la forme d'un couple numérateur/dénominateur, et peut donc obtenir une précision arbitraire… et surtout un résultat correct. On peut même comparer le résultat à 0 :
>
say
"Vrai"
if $l
'addition-devrait-être-zéro == 0;
Vrai
Tout développeur un tant soit peu expérimenté sait qu'il ne faut surtout pas faire ce genre de comparaison dans la plupart des langages de programmation. En Perl 6, ça marche parfaitement. Cet exemple montre la mise en pratique du principe « Do What I Mean » (DWIM) cher aux concepteurs du langage Perl 6 : on s'attend normalement à ce que cette opération soit égale à 0, mais on constate que ce n'est pas le cas dans la plupart des langages de programmation. Perl 6 « fait exactement ce que nous voulons dire ». Ce simple problème d'addition est un premier exemple de ce qu'un langage radicalement moderne devrait faire : sauf peut-être si nous développons des microcodes pour une puce embarquée, nous ne devrions plus, en 2016, avoir à nous demander si notre ordinateur sait compter correctement, ce devrait être acquis depuis longtemps. Avec Perl 6, votre ordinateur sait enfin le faire.
L'exemple ci-dessus illustre au passage quelques autres caractéristiques de Perl 6. Comme en Perl 5 (et en PHP), les noms de variables commencent par un caractère spécial, nommé sigil, ici le signe $ pour une variable scalaire, suivi de l'identifiant proprement dit, qui doit commencer par un caractère alphabétique ou le caractère de soulignement « _ ». Elles doivent être déclarées avant d'être utilisées, ici avec le mot-clef my. Perl 6 supporte pleinement l'Unicode, et les identifiants de variables ou de fonctions peuvent contenir des lettres françaises accentuées (ci-dessus, le « ê » et le « é »), mais on aurait aussi bien pu utiliser des lettres grecques :
>
my $
φ =
(5
**
.5
+
1
)/
2
; # nombre d'or
1
.61803398874989
>
say
"Le nombre d'or est égal à $φ."
;
Le nombre d'or est égal à 1.61803398874989.
> my $π = 4 * atan 1;
3.14159265358979;
ou d'un quelconque autre alphabet. Là encore, la modernité… De plus, comme on le voit dans le premier exemple de code, ces identifiants peuvent également contenir des tirets (« - ») ou des apostrophes (« ' »), à condition qu'ils soient situés entre deux lettres.
On constatera également dans les exemples ci-dessus que Perl 6 n'a généralement pas besoin de beaucoup de parenthèses, sauf quand c'est nécessaire pour des raisons de précédence des opérateurs (comme dans la formule du nombre d'or). Par exemple, les arguments d'une fonction interne peuvent généralement être placés sans parenthèses après le nom de la fonction appelée (et séparés par des virgules s'il y en a plusieurs). De même, on peut souvent enchaîner plusieurs fonctions comme dans l'exemple say sprintf ... ci-dessus, Perl 6 sait très bien prendre la ou les valeurs de retour d'une fonction et la ou les passer en argument à la fonction placée à sa gauche. Cela rend la syntaxe souvent plus fluide et plus limpide.
Le caractère « # » introduit un commentaire s'étendant jusqu'à la fin de la ligne. Il existe d'autres formes de commentaires pouvant être placés au milieu d'une ligne de code ou pouvant couvrir plusieurs lignes, par exemple :
say
#`(Commentaire au milieu d'une ligne de code)
"Hello World !"
; # imprime Hello World !
say
#`[Ceci est
un commentaire
multiligne]
"Hello World !"
; # idem
mais nous n'entrerons pas plus dans les détails ici.
3. Les variables▲
3-1. Les diverses sortes de variables▲
Il existe trois principales sortes de variables, qui se distinguent par le sigil précédant l'identifiant proprement dit :
- le sigil « $ » indique une variable scalaire, c'est-à-dire une variable ne contenant qu'une seule valeur (par exemple une valeur numérique, une chaîne de caractères, un objet ou une référence vers une autre entité) ;
- le sigil « @ » désigne une variable de tableau, c'est-à-dire une liste de valeurs indexées par des entiers ;
- le sigil « % » dénote une table de hachage, qui est définie comme un ensemble de paires clef-valeur.
On accède aux éléments d'un tableau avec l'opérateur […] et à ceux d'un hachage avec {…}. Contrairement à Perl 5, une variable de tableau ou de hachage conserve son sigil d'origine quand on accède à ses éléments individuels :
3-2. Portée des variables▲
Nous avons vu que le mot-clef my sert à déclarer une variable. Plus précisément, il introduit une variable lexicale, c'est-à-dire une variable dont la portée est limitée au bloc de code dans lequel elle se trouve. Un bloc de code est, en simplifiant un peu, un fragment de code délimité par des accolades ouvrantes et fermantes servant à structurer le code. Une variable lexicale est visible et utilisable entre l'endroit où elle est déclarée avec le my et l'accolade fermant le bloc où se trouve la déclaration. Si la déclaration a lieu à l'extérieur de tout bloc, alors la variable est globale au script (ce qui n'est généralement pas conseillé).
{
my Str $var
=
"texte"
;
say
$var
; # affiche "texte"
}
say
$var
; # ERREUR: $var n'est plus accessible
On peut aussi avoir deux variables de même nom ayant des valeurs différentes selon la portée dans laquelle elles se trouvent :
my Int var =
42
;
{
my Str $var
=
"texte"
;
say
$var
; # affiche "texte"
}
say
$var
; # -> 42
Ces exemples sont un peu artificiels à ce stade, mais cette possibilité s'avérera très utile quand nous utiliserons des blocs décrivant des boucles ou le corps de fonctions.
3-3. Les types de données▲
Une variable peut être déclarée avec un type imposant des contraintes sur ses valeurs possibles :
my Int $c
;
$c
=
4
; # tout va bien, $c vaut 4
say
$c
.WHAT # (Int)
$c
=
4
.2
; # ERREUR : Type check failed in assignment to $c...
Mais la déclaration n'est pas nécessaire et Perl 6 détermine dans ce cas lui-même le type de la donnée dans la mesure du possible :
my $d
=
4
.2
; # Pourrait aussi s'écrire : my $d = Rat.new(42, 10);
say
$d
.WHAT; # (Rat)
say
'$d = '
, $d
.numerator, " / "
,
$d
.denominator; # affiche : $d = 21 / 5
On dit que Perl 6 est typé de façon « graduelle » : il autorise aussi bien un typage statique (comme C ou Java) qu'un typage dynamique (comme Perl 5, Ruby ou Python). Le meilleur de deux mondes, en quelque sorte. En fait, les variables $c et $d ci-dessus sont assimilables à des objets, respectivement de type Int et Rat, ce qui explique la possibilité d'invoquer sur eux les méthodes .WHAT ou .numerator. Pour les types les plus courants, il n'est souvent pas nécessaire d'invoquer le constructeur .new pour les obtenir. Ainsi, il existe plusieurs façons de créer des objets de type complexe :
my $z1
=
5
+
3i;
say
$z1
.WHAT; # (Complex)
my $z2
=
Complex.new(4
, 9
); # 4+9i
my Complex $z3
=
2
+
5i;
say
$z1
+
$z2
+
$z3
; # affiche : 11+17i
L'utilisation d'une simple affectation est suffisante pour créer un objet du type voulu, mais les deux syntaxes utilisant explicitement le type Complex sont un peu plus sûres, car elles vérifient le type du littéral affecté à la variable. De même, considérons des objets de type Date :
my $d
=
Date.new(2015
, 12
, 24
); # Veille de Noël : 2015-12-24
say
$d
.year; # 2015
say
$d
.month; # 12
say
$d
.day; # 24
say
$d
.day-
of-
week; # 4 (donc, jeudi)
my $n
=
Date.new('2015-12-31'
); # Saint-Sylvestre
say
$n
>
$d
; # True
say
$n
-
$d
; # 7 (delta 7 jours)
say
$n
+
1
; # 2016-01-01
say
1
+
$n
.later(:2months); # 2016-03-01
Remarquez au passage combien la manipulation des dates devient facile et intuitive. C'est cela aussi la modernité de Perl 6. Ici, la syntaxe utilisant le constructeur new était nécessaire parce que sans elle, le programme ne pourrait reconnaître une date et effectuerait simplement une double soustraction entre entiers :
>
my $d
=
2015
-
12
-
25
; # Non, ce n'est pas Noël
1978
3-4. Les variables scalaires▲
Les variables scalaires, introduites par le sigil $, ne contiennent qu'un seul élément (mais cet élément peut lui-même être composite, par exemple si c'est un objet comme nous venons de le voir dans les exemples ci-dessus). Les variables scalaires contiennent souvent des chaînes de caractères ou des nombres.
3-4-1. Chaînes de caractères▲
Les chaînes de caractères sont des séquences immuables de caractères quelconques. Elles sont généralement encadrées par des guillemets ou des apostrophes. La différence est que les variables (et les séquences d'échappement) sont interpolées dans une chaîne de caractères entre guillemets, mais pas dans celles entre apostrophes.
my $user
=
'Laurent'
;
say
"Bonjour,
$user
!"
; # Bonjour Laurent !
say
'Bonjour, $user !'
; # Bonjour $user !
Le tilde « ~ » est l'opérateur de concaténation de chaînes :
say
"Hello "
~
"World !"
; # Hello World !
Il existe de nombreuses fonctions ou méthodes travaillant sur des chaînes :
my $nom
=
"Charlie"
;
say
flip $nom
; # eilrahC (syntaxe fonctionnelle)
say
$nom
.flip; # eilrahC (syntaxe de méthode)
say
uc
$nom
; # CHARLIE
say
$nom
.uc
; # idem
say
$nom
.chars; # 7 (nombre de caractères)
say
substr
$nom
, 2
, 3
; # arl (sous-chaîne)
say
"Je suis "
~
$nom
; # Je suis Charlie (concaténation)
On remarque que les sous-routines internes de Perl 6 admettent pour la plupart une syntaxe de fonction et une syntaxe de méthode. Il est possible de les combiner à volonté, notamment pour clarifier l'intention ou la précédence :
>
say
flip "Charlie"
.substr
(2
, 3
).uc
LRA
3-4-2. Données numériques▲
Nous avons déjà vu des exemples de données numériques de types Int (entier) ou Rat (rationnel). La racine carrée de 2, le logarithme de 5 ou 1017 sont de type Num :
De nombreuses fonctions ou méthodes permettent de travailler sur les données numériques ou seulement sur certains types numériques :
say
19
.is-prime
; # True (19 est premier)
say
4
.7
.nude; # (47 10), c'est-à-dire 47/10
Cela n'est pas spécifique aux variables numériques, mais notons au passage qu'il est possible de définir dynamiquement des « sous-types » ou sous-ensembles de types existants. On peut par exemple créer un type nombre impair :
3-5. Les tableaux▲
Les tableaux sont des listes contenant des valeurs multiples. Les valeurs n'ont pas besoin d'être du même type (on peut par exemple mélanger des chaînes et des nombres), mais c'est souvent une bonne idée qu'elles le soient dans la mesure où les éléments d'un tableau doivent en principe avoir une certaine cohérence sémantique. On accède aux valeurs individuelles d'un tableau à l'aide d'indices qui sont des nombres entiers, le premier élément d'un tableau portant l'indice 0.
my @nombres_shadoks
=
['GA'
, 'BU'
, 'ZO'
, 'MEU'
];
say
@nombres_shadoks
[1
]; # imprime: BU
Si les éléments d'un tableau sont des chaînes de caractères sans espace, on peut utiliser un opérateur de citation de liste <...> pour écrire plus simplement, sans guillemets, apostrophes, ni virgules :
>
my @nombres_shadoks
=
<
GA BU ZO MEU>
;
[GA BU ZO MEU]
>
say
@nombres_shadoks
.elems; # nombre d'éléments
4
>
my $dernier
=
pop
@nombres_shadoks
;
MEU
>
say
@nombres_shadoks
;
[GA BU ZO]
>
say
elems @nombres_shadoks
; # nombre d'éléments
3
>
say
"Les shadoks ont perdu leur dernier chiffre:
$dernier
"
;
Les shadoks ont perdu leur dernier chiffre: MEU
>
push
@nombres_shadoks
, $dernier
;
[GA BU ZO MEU]
La fonction pop retire le dernier élément du tableau et le renvoie ; la fonction push ajoute un (ou plusieurs) élément(s) à la fin d'un tableau. On aurait aussi pu utiliser une syntaxe de méthode :
>
my $dernier
=
@nombres_shadoks
.pop
MEU
>
say
@nombres_shadoks
[GA BU ZO]
>
@nombres_shadoks
.push
($dernier
);
[GA BU ZO MEU]
La fonction splice(a, n) retire n élément(s) d'un tableau à partir de la position a et les renvoie :
>
my @nombres_shadoks
=
<
GA BU ZO MEU>
;
[GA BU ZO MEU]
>
my @elem_1_2
=
splice
@nombres_shadoks
, 1
, 2
;
[BU ZO]
Mais si l'on désire récupérer collectivement plusieurs éléments d'un tableau sans modifier le tableau, on peut utiliser une syntaxe de tranches de tableaux :
my @nombres_shadoks
=
<
GA BU ZO MEU>
;
say
@nombres_shadoks
[0
..2
]; # (GA BU ZO)
Il existe de nombreuses autres fonctions ou méthodes utilisables, fournies notamment par les classes internes Array et List de Perl.
Les tableaux peuvent être multidimensionnels. On peut accéder aux éléments d'un tableau multidimensionnel en séparant les indices par un « ; » :
my $prem-rang_deux-col
=
@tableau
[0
;1
];
3-6. Les hachages▲
Un hachage est un ensemble de paires clef-valeur. L'idée générale est la même que celle des dictionnaires ou maps dans d'autres langages.
>
my %capitales
=
("Italie"
, "Rome"
, "Allemagne"
, "Berlin"
, "Espagne"
, "Madrid"
);
Allemagne =>
Berlin, Espagne =>
Madrid, Italie =>
Rome
On peut rendre le code plus concis avec une syntaxe de liste :
>
my %capitales
=
<
Italie Rome Allemagne Berlin Espagne Madrid>
;
Allemagne =>
Berlin, Espagne =>
Madrid, Italie =>
Rome
et plus clair en utilisant l'opérateur de construction de paires => dès l'initialisation :
my %capitales
=
(Italie =>
"Rome"
, Allemagne =>
"Berlin"
, Espagne =>
"Madrid"
);
L'opérateur => a essentiellement le même rôle qu'une virgule, mais elle rend l'intention plus claire et dispense de mettre la clef entre guillemets.
On peut ajouter un nouvel élément au hachage par une affectation avec une nouvelle clef :
%capitales
{
"France"
}
=
"Paris"
;
Ou :
%capitales<France>
=
"Paris"
;
La méthode push permet également d'ajouter une nouvelle paire au hachage :
push
%capitales
, (Danemark =>
"Copenhague"
);
%capitales
.push: (USA =>
"Washington"
);
say
%capitales
.elems ; # 6 (nombre de paires)
Les méthodes kv, keys et values renvoient respectivement des listes de paires, de clefs et de valeurs :
>
say
%capitales
.kv
(France Paris Allemagne Berlin Italie Rome USA Washington Danemark Copenhague Espagne Madrid)
>
say
%capitales
.keys
;
(France Allemagne Italie USA Danemark Espagne)
>
say
%capitales
.values
;
(Paris Berlin Rome Washington Copenhague Madrid)
Notons qu'un hachage stocke et restitue les paires dans un ordre apparemment aléatoire (indépendant de celui dans lequel elles ont été créées), mais les trois méthodes renvoient les éléments dans un ordre consistant.
4. Les opérateurs▲
Perl 6 possède la plupart des opérateurs communs aux langages de programmation usuels et de nombreux autres.
4-1. Principaux opérateurs▲
Le tableau ci-dessous résume les opérateurs les plus communs, par ordre de précédence descendante (de la priorité la plus forte à la plus faible) :
.method |
Méthode postfixée |
++ -- |
Auto-incrémentation, autodécrémentation (préfixées ou postfixées) |
** |
Exponentielle |
! + - ~ ? | || |
Symboles unaires : négation logique, plus, moins, concaténation, coercition booléenne, ou logique |
* / % %% div gcd lcm |
Multiplication, division, modulo, divisibilité, division entière, PGDC, PPMC |
+ - |
Addition, soustraction |
x xx |
Réplication de chaîne (renvoie une chaîne), réplication d'élément (renvoie une liste) |
~ |
Concaténation de chaînes |
~~ != == < <= > >= eq ne lt le gt ge |
Opérateur de comparaison intelligente, opérateurs de comparaisons numériques, opérateurs de comparaisons de chaînes |
&& |
et booléen (de haute précédence) |
|| |
ou booléen (de haute précédence) |
?? !! |
Opérateur conditionnel ternaire |
= => += -= **= xx= .= |
Opérateurs d'affectation |
so not |
Booléens unaires de basse précédence : coercition booléenne et non logique |
and andthen |
et booléen (de basse précédence) |
or xor orelse |
ou booléen (de basse précédence) |
Nous verrons plus loin qu'il est facile de construire ses propres opérateurs.
4-2. On en a rêvé, Perl 6 le fait▲
Les opérateurs de comparaison logique peuvent être chaînés comme en arithmétique, ce qui peut simplifier notablement l'écriture :
say
"Valeurs dans l'ordre"
if $u
<
$v
<
$x
<
$y
<
$z
;
# équivaut à : ... if $u < $v and $v < $x and $x < $y ...
# ou à : ... if ($u < $v) and ($v < $x) and ...
De même, les jonctions permettent d'écrire des comparaisons concises de ce style :
4-3. Les métaopérateurs et hyperopérateurs▲
Les opérateurs travaillent sur des données, les métaopérateurs travaillent sur des opérateurs et permettent en quelque sorte de créer de nouveaux opérateurs.
Le métaopérateur de réduction […] permet de transformer un opérateur infixé associatif en un opérateur de liste renvoyant un scalaire :
>
say
[+
] 1
, 2
, 3
, 4
;
10
Ici, tout se passe comme si on avait placé l'opérateur + entre chaque élément de la liste, comme si on avait écrit :
>
say
1
+
2
+
3
+
4
;
10
On peut de même calculer très facilement la factorielle de 10 en modifiant l'opérateur de multiplication :
my $fact10
=
[*
] 1
..10
; # -> 3628800
Il existe d'autres métaopérateurs. Par exemple, le métaopérateur X renvoie un produit cartésien entre deux ou plusieurs listes :
>
<
a b c>
X 1
, 2
;
((a 1
) (a 2
) (b 1
) (b 2
) (c 1
) (c 2
))
Un hyperopérateur applique une opération à chaque membre d'une liste ou de plusieurs listes et renvoie une nouvelle liste. Ici, chaque élément de la liste est multiplié par 5 :
>
my @a
=
6
..10
;
[6
7
8
9
10
]
>
say
5
«*
» @a
;
[30
35
40
45
50
]
À noter que cet hyperopérateur utilise en principe les guillemets français «...», mais vous pouvez utiliser les chevrons ASCII <<...>> si votre éditeur ne vous permet pas d'écrire facilement ces guillemets français.
Avec deux ou plusieurs listes, l'hyperopérateur permet d'effectuer des opérations membre à membre sur les listes. Voici par exemple une concaténation membre à membre entre trois listes :
>
my @x
=
('a'
..'e'
) «~
» (3
..7
) «~
» ('v'
..'z'
);
[a3v b4w c5x d6y e7z]
Les métaopérateurs et hyperopérateurs créent de nouveaux opérateurs en modifiant la sémantique d'opérateurs existants. Nous verrons plus loin qu'il est possible de créer facilement des opérateurs entièrement nouveaux. Tout cela tend à rendre le langage intrinsèquement malléable et extensible.
4-4. Intermède : quelques exercices▲
Pour assimiler un nouveau langage, rien ne vaut la pratique. Voici donc quelques exercices vous permettant de tester votre acquisition de connaissances.
- Exercice 1 : la fonction interne lcm renvoie le plus petit multiple commun (PPMC) entre deux nombres. Écrire un programme qui affiche le plus petit nombre positif divisible par tous les nombres de 1 à 20.
- Exercice 2 : écrire un programme qui calcule la somme des chiffres du nombre factorielle de 100 ;
- Exercice 3 : trouver la différence entre le carré de la somme des 100 premiers entiers naturels et la somme des carrés des 100 premiers entiers.
Nous n'avons pas encore étudié les conditions (if, etc.), ni les boucles (for, while, etc.), ni les fonctions. Il faut donc trouver dans ce que nous avons déjà appris d'autres solutions pour résoudre ces problèmes. Un indice : relisez tout le chapitre sur les opérateurs. Nous ne relèverons pas les copies, mais essayez vraiment de faire ces exercices.
Lorsque, au début de mes études d'informatique, j'ai dû faire ce genre d'exercices dans le langage de programmation choisi à l'époque par mes enseignants (Pascal ou C, par exemple), je m'attendais à devoir écrire quelques dizaines de lignes de code pour chacun d'entre eux. Ici, en Perl 6 et avec un modèle de programmation issu de la programmation fonctionnelle, chacun peut se faire en une seule petite ligne de code. Voilà notamment ce que nous voulons dire quand nous disons que Perl 6 est un langage puissant et expressif.
Vous trouverez des solutions à ces exercices dans l'encadré plus loin.
5. Structures de contrôle : conditions et boucles▲
5-1. Les conditions simples▲
La condition if / else est classique. Sa seule particularité est que, contrairement à beaucoup de langages, elle ne nécessite pas de parenthèses autour de la condition :
my $âge
=
19
;
if $âge
>=
18
{
say
"Vous êtes un adulte."
}
else {
say
"Vous êtes un jeune."
}
À noter que le point-virgule n'est pas nécessaire avant une accolade fermant un bloc.
On peut également tester successivement plusieurs conditions avec des clauses elsif :
if $âge
<
12
{
say
"Enfant"
}
elsif $âge
<
18
{
say
"Adolescent"
}
else {
say
"Adulte"
}
Une condition if peut également se placer après l'instruction (on parle alors d'instruction modifiée) :
>
say
"Bienvenue sur ce site"
if $âge
>=
18
;
Bienvenue sur ce site
Cette syntaxe est concise et pratique, mais n'autorise pas de clause else ou elsif.
La condition unless inverse la condition :
say
"Désolé : réservé aux adultes !"
unless $âge
>=
18
;
# équivalent à :
say
"Désolé : réservé aux adultes !"
if not
$âge
>=
18
;
La construction given … when correspond au switch d'autres langages, mais permet une formulation beaucoup plus riche et variée des conditions :
my Int $âge
=
18
;
given $âge
{
when *
..^
0
{
say
"âge négatif ? Hum..."
}
;
when 0
..2
{
say
"Bébé"
}
;
when 3
..12
{
say
"Enfant"
}
;
when *
..17
{
say
"Adolescent"
}
;
when 18
{
say
"tout jeune adulte"
}
;
default {
say
"Adulte"
}
}
Dès que l'une des conditions est satisfaite, les conditions suivantes ne sont pas évaluées. La condition *..17, c'est-à-dire compris entre 0 et 17, est donc ici correcte et en fait équivalente à 13..17 puisque l'on n'arrive à cette condition que si les trois conditions précédentes ont échoué (ce qui ne veut pas dire qu'il soit recommandé d'écrire une telle bizarrerie contre-intuitive, donnée ici à seul titre d'exemple).
On peut inviter le programme à continuer l'évaluation des conditions suivantes même si une condition est satisfaite avec une instruction proceed :
my $var
=
42
;
given $var
{
when 0
..50
{
say
'Compris entre 0 et 50'
; proceed }
;
when Int {
say
"Un entier"
; proceed}
;
when /4/
{
say
"Contient le chiffre 4"
; proceed }
;
when not
.is-prime
{
say
"Non premier"
; proceed }
;
when 42
{
say
"réponse à la Grande Question"
}
}
Ici, grâce aux instructions proceed, chacun des cinq messages est affiché l'un après l'autre puisque toutes les conditions sont successivement satisfaites.
5-2. L'opérateur ternaire▲
L'opérateur ternaire, issu du langage C, utilise ?? et !! :
>
my ($x
, $y
) =
(1
, 2
);
(1
2
)
>
say
"Max = "
, $x
>
$y
??
$x
!!
$y
;
Max =
2
On peut enchaîner plusieurs de ces opérateurs :
my Int $âge
=
18
;
say
$âge
<=
2
??
"Bébé"
!!
$âge
<=
12
??
"Enfant"
!!
$âge
<
18
??
"Ado"
!!
$âge
==
18
??
"Jeune adulte"
!!
"Adulte"
;
ce qui imprime « Jeune adulte ».
Solution des exercices
Voici des solutions possibles aux exercices proposés dans la section 4.4. ci-dessus.
PPMC des nombres de 1 à 20
Le métaopérateur de réduction […] permet d'appliquer un opérateur successivement à tous les éléments d'une liste. Il suffit de l'utiliser avec l'opérateur lcm sur la liste des nombres de 1 à 20 :
>
say
[lcm] 1
..20
;
232792560
Somme des chiffres de factorielle 100
Nous utilisons ici deux fois le métaopérateur de réduction […] : une première fois avec la multiplication pour calculer 100! et une seconde fois pour faire la somme des chiffres du résultat. Ce qui donne :
>
say
[+
] split
''
, [*
] 2
..100
;
648
Carré de la somme moins somme des carrés
Perl 6 calcule facilement la somme des 100 premiers nombres avec [+] 1..100. L'hyperopérateur «...» permet de calculer les carrés des cent premiers entiers et le métaopérateur […] de réduire cette liste de carrés à leur somme. Ce qui permet d'écrire :
say
([+
] 1
..100
)**
2
-
[+
] (1
..100
) «**
» 2
;
25164150
On pourrait alléger le calcul en remarquant que la somme des 100 premiers entiers naturels est égale à (100 * 101) / 2 = 5050, mais le but était ici surtout d'employer les hypeopérateurs et métaopérateurs du langage.
5-3. Les boucles▲
En Perl 6, la boucle la plus commune est la boucle for, qui itère sur une liste de valeurs ou les éléments d'un tableau et dont la syntaxe la plus commune est la suivante :
>
my @tableau
=
[0
..10
];
[0
1
2
3
4
5
6
7
8
9
10
]
>
for @tableau
->
$val
{
print
$val
*
2
, " "
}
;
0
2
4
6
8
10
12
14
16
18
20
>
Cette construction s'appelle un « bloc pointu ». Ici, la variable $val est un alias en lecture seule sur les valeurs successives du tableau ; elle n'a pas besoin d'être prédéclarée et sa portée est naturellement limitée au bloc d'instructions de la boucle for. Le lecteur féru de programmation fonctionnelle aura peut-être reconnu dans cette construction de « bloc pointu » à la fois une lambda et une fermeture anonyme.
La boucle for ci-dessus itère directement sur les éléments du tableau, ce qui correspond au besoin le plus fréquent. Si l'on a besoin d'itérer sur les indices, il suffit de créer à la volée une liste des indices à l'aide de l'opérateur intervalle .. et de la fonction ou méthode end (retournant l'indice du dernier élément d'un tableau) et d'itérer sur cette liste :
my @mois
=
<
none jan fév mar avr mai jun>
;
for 1
..end @mois
->
$num
{
say
"
$num
\t
@mois
[
$num
]"
}
;
# ou: for 1..@mois.end -> ...
On peut également utiliser les itérateurs de listes du langage, par exemple keys pour récupérer la liste d'indices :
my @nombres
=
<
zéro un deux trois quatre cinq>
;
for @nombres
.keys
->
$indice
{
say
"
$indice
\t
@nombres
[
$indice
]"
}
;
ou kv pour récupérer à la fois les indices et les éléments correspondants :
for @nombres
.kv ->
$indice
, $nombre
{
say
"
$indice
\t
$nombre
"
}
;
Si l'on a besoin de modifier les valeurs du tableau, il faut un alias en lecture et écriture, ce qui se fait à l'aide d'un bloc « doublement pointu » :
my @tableau
=
[0
..10
];
for @tableau
<->
$val
{
$val
*=
3
}
say
@tableau
; # affiche [0 3 6 ... 30]
Une syntaxe d'expression modifiée est également possible :
>
print
$_
*
2
, " "
for [0
..10
];
0
2
4
6
8
10
12
14
16
18
20
>
Une boucle for est un itérateur et peut donc travailler de façon « paresseuse » (c'est-à-dire qu'elle ne traite les éléments qu'au fur et à mesure des besoins), même sur une liste « infinie ». Employer une boucle for est donc une façon idiomatique et performante d'itérer sur les lignes d'un fichier :
La boucle while ressemble à celle des autres langages communs et exécute la boucle tant que la condition est vraie, si ce n'est que les parenthèses ne sont pas nécessaires autour de la condition :
Une boucle until exécute le bloc de la boucle tant que la condition est fausse. Voici un exemple utilisant une syntaxe d'instruction modifiée :
Vous pouvez enfin utiliser une boucle loop, qui correspond à la boucle for du langage C et des langages apparentés :
Cette syntaxe permet de construire des boucles complexes, mais il est rare d'avoir besoin de ce genre de boucle en Perl 6 : la boucle for de Perl 6 est généralement bien plus pratique. C'est la seule construction de boucle pour laquelle les parenthèses restent nécessaires autour des conditions/initialisations. Sa seule utilisation fréquente est la façon idiomatique d'écrire une boucle infinie :
loop {
…}
Les instructions next et last permettent, respectivement, de recommencer à l'itération suivante et de sortir immédiatement d'une boucle for, while ou autre :
6. Fonctions ou sous-routines▲
Les fonctions permettent de regrouper un ensemble de fonctionnalités. On déclare une fonction à l'aide du mot-clef sub (pour subroutine) suivi de son identifiant (son nom), et l'on définit ce qu'elle fait dans un bloc de code placé entre accolades.
saluer(); # imprime "Bonjour ..."
sub saluer {
say
'Bonjour tout le monde.'
;
}
Contrairement à certains langages, la fonction peut être appelée avant sa définition. L'instruction saluer(); est l'appel de la fonction. C'est lors de l'exécution de cette ligne de code que la fonction s'exécute.
6-1. Arguments et signatures▲
La fonction ci-dessus ne prend pas de paramètres en entrée et ne renvoie pas de valeurs de retour, ce qui limite quelque peu son utilité. La plupart des fonctions utiles ont besoin de données en entrée, qui leur sont fournies sous la forme d'arguments. Voici une fonction analogue avec le passage d'un argument :
saluer("Maître"
); # imprime "Bonjour Maître."
sub saluer ($titre
) {
say
"Bonjour
$titre
."
;
}
6-1-1. Nombre d'arguments d'une fonction▲
Ici, l'appel de la fonction se fait avec un argument, la chaîne "Maître". La définition de la fonction contient maintenant une signature placée entre parenthèses ; cette signature définit la variable $titre comme le paramètre (formel) que reçoit la fonction. Dans cet exemple, le paramètre $titre prend la valeur de l'argument d'appel, "Maître", et est utilisé ensuite dans le corps de la fonction. Cette signature est réduite à sa plus simple expression, mais elle a déjà pour effet d'obliger le code utilisateur à passer un argument (et un seul) lors de l'appel à la fonction. Si le nombre d'arguments est différent de un, cela génère une erreur.
Par défaut, les paramètres des fonctions sont des copies en lecture seule des arguments reçus (c'est une forme de passage de paramètre par valeur) et ne peuvent donc pas être modifiés dans la fonction. Mais on peut changer ce comportement grâce à des traits (propriétés définies à la compilation) tels que is rw (lecture écriture, comme lors d'un passage de paramètres par référence) ou is copy (copie locale à la fonction et modifiable) :
6-1-2. Type des arguments▲
La signature permet non seulement de vérifier le nombre d'arguments, mais aussi leur type. Voici un exemple de fonction ayant une signature un peu plus complexe :
6-1-3. Paramètres positionnels▲
Ici, la signature comporte deux paramètres, $x et $y. Ce sont des paramètres positionnels : le paramètre $x reçoit la valeur du premier argument passé à la fonction et le paramètre $y reçoit le second argument. En outre, ces deux paramètres sont de type entier. Le compilateur vérifiera donc que la fonction est bien appelée avec deux arguments de type entier, sinon il émettra une erreur. À noter que si la fonction est comme ici déclarée avant son appel, il est possible d'appeler la fonction en omettant les parenthèses autour des arguments :
multiplie 3
, 4
; # -> Produit = 12
Cette fonction ne peut multiplier que des entiers, ce qui en limite l'utilité. Il est bien sûr possible de définir des signatures sur des types numériques quelconques, par exemple en utilisant le type générique Numeric.
6-1-4. Paramètres nommés▲
Il peut être difficile de se souvenir de l'ordre des paramètres positionnels, surtout quand la fonction accepte de nombreux arguments. Il est dans ce cas possible d'utiliser des paramètres nommés rendant l'ordre des arguments indifférents, avec la syntaxe suivante :
sub divise (Numeric :$dividende
, Numeric :$diviseur
) {
say
$dividende
/
$diviseur
}
divise(dividende =>
12
, diviseur =>
4
); # -> 3
divise diviseur =>
4
, dividende =>
12
; # idem
Le second exemple d'appel de la fonction divise ci-dessus montre que les parenthèses sont ici encore optionnelles si la fonction a été déclarée avec sa signature avant son appel.
6-2. Fonctions multiples▲
Il est possible de définir avec le mot-clef multi des fonctions spéciales ayant le même nom, mais se distinguant par leur signature (nombre et type des arguments). Perl détermine quel exemplaire de la fonction appeler en fonction de la signature (processus analogue à la résolution des méthodes en programmation orientée objet).
multi salue ($nom
) {
say
"Bonjour
$nom
"
}
multi salue ($nom
, $titre
) {
say
"Bonjour
$titre
$nom
"
}
salue("George Lucas"
); # -> Bonjour George Lucas
salue "Yoda"
, "Maître"
; # -> Bonjour Maître Yoda
Ici, c'est le nombre d'arguments de la fonction qui permet à Perl 6 de déterminer quelle version de salue appeler. La distinction pourrait aussi se faire sur le type (voire le « sous-type »).
Beaucoup des opérateurs et des fonctions ou méthodes internes de Perl 6 sont définis comme des fonctions multiples. Cela signifie qu'en utilisant une signature n'existant pas en interne, il est possible de redéfinir ou surcharger ces fonctions ou opérateurs.
6-3. Valeurs de retour▲
Toutes nos fonctions jusqu'ici se contentaient d'afficher quelque chose à l'écran. Très souvent, il est souhaitable qu'une fonction se contente de prendre des paramètres en entrée et de renvoyer des valeurs de retour, sans avoir d'effet de bord. Cela facilite la conception et la mise au point de programmes.
Voici la définition et l'utilisation d'une fonction renvoyant une valeur utile :
>
sub carré ($x
) {
return $x
*
$x
}
sub carré ($x
) {
#`(Sub|183561872)
... }
>
say
carré 3
9
Le mot-clef return indique explicitement que la fonction doit se terminer et renvoyer la valeur donnée. Et l'appel carré 3 renvoie à say la valeur 9. En fait, ce mot-clef n'était pas indispensable ici, car une fonction renvoie implicitement la dernière expression évaluée, si bien que la fonction aurait aussi bien pu s'écrire :
sub carré ($x
) {
$x
**
2
}
Cela suffit pour des fonctions simples n'ayant qu'un seul point de sortie, mais l'utilisation de return permet de définir finement le comportement d'une fonction et les valeurs de retour selon les conditions rencontrées.
7. Définir ou redéfinir un opérateur▲
Les opérateurs de Perl 6 sont en fait des fonctions ou des méthodes ayant souvent un nom un peu inhabituel et définissant quelques propriétés permettant de les utiliser : un opérateur est généralement doté d'une précédence (priorité d'exécution), d'un type de notation syntaxique (préfixée, postfixée, infixée, etc.) et d'une associativité (comment il se comporte quand il y a plusieurs opérateurs de même précédence). Pour créer un nouvel opérateur, il faut au minimum définir son type de notation, et, selon le besoin, éventuellement préciser sa précédence et son associativité.
Nous pourrions par exemple utiliser le symbole euro « € » pour définir un opérateur de doublement d'un entier :
Ce nouvel opérateur n'est sans doute pas très utile (et son nom n'est pas idéalement choisi pour ce qu'il fait), mais illustre simplement comment il est possible d'enrichir dynamiquement le langage.
À titre d'exemple d'un nom d'opérateur peut-être mieux choisi, utilisons le petit « 2 » en indice du clavier pour définir un opérateur d'élévation au carré :
sub postfix:<
²>
(Numeric $n
) {
$n
**
2
}
say
5
²; # -> 25
say
(3
+
4
)²; # -> 49
Perl 6 supportant très bien les caractères Unicode, il est possible d'utiliser les symboles mathématiques (par exemple le symbole racine carrée), les lettres grecques, tibétaines ou d'autres langues, les guillemets japonais 「」, les pictogrammes normalisés, les caractères braille, etc. pour définir des opérateurs.
Un opérateur n'est pas nécessairement un caractère spécial unique, nous pourrions (ou du moins aurions pu, il y a quelques années, ce n'est plus très utile aujourd'hui) définir des opérateurs de conversion de francs en euros et réciproquement :
sub prefix:<
f€>
(Numeric $n
) {
$n
/
6
.55957
}
sub prefix:<
€f>
(Numeric $n
) {
$n
*
6
.55957
}
say
f€ 6
.56
; # -> 1.0000656
say
€f 10
; # -> 65.5957
Définir un opérateur infixé ne pose pas plus de problèmes. Par exemple, voici un opérateur de calcul de la moyenne entre deux nombres :
sub infix:<moy>
(Numeric $n
, Numeric $m
) {
($n
+
$m
)/
2
}
say
10
moy 5
; # -> 7.5
L'opérateur « ! » de négation booléenne est de type préfixé, c'est-à-dire qu'il se place avant le terme auquel il s'applique. Nous pouvons réutiliser le même opérateur « ! » pour définir la factorielle, qui utilisera naturellement, comme en mathématiques, une notation postfixée :
sub postfix:<!>
(Int $n
) {
[*
] 2
..$n
}
say
20
!
; # -> 2432902008176640000
say
!
False; # -> True (la négation booléenne fonctionne toujours)
Voici enfin un exemple dans lequel nous définissons aussi la précédence de l'opérateur. Il existe en Perl 6 un type Pair qui définit une paire clef-valeur et se note généralement clef => valeur. Nous pourrions vouloir utiliser ce type pour modéliser des couples de valeurs que nous désirons pouvoir additionner membre à membre. Il suffit de définir l'addition de paires et, tant qu'à faire, lui donner la même propriété de précédence que l'addition arithmétique :
multi sub infix:<+>
(Pair $x
, Pair $y
) is equiv(&
infix:<+>
) {
return $x
.key +
$y
.key =>
$x
.value +
$y
.value
}
my $a
=
4
=>
3
;
my $b
=
7
=>
2
;
say
$a
+
$b
; # -> 11 => 5
Le « trait » is equiv(&infix:<+>) précise que cet opérateur a la même précédence que l'opérateur d'addition. Si nous définissons une multiplication membre à membre sur le même modèle en lui donnant la même précédence que la multiplication, nous retrouverons les règles de précédence habituelles entre nos opérations sur les paires.
La création de nouveaux opérateurs est un moyen simple d'enrichir dynamiquement le langage. Pour des enrichissements plus complexes, il est possible de modifier dynamiquement la grammaire même de Perl 6, mais cela nécessiterait de présenter les grammaires de Perl et sortirait du cadre de cette brève présentation.
8. Conclusion▲
Nous avons fait un bref tour d'horizon de la syntaxe de base de Perl 6 et avons aussi essayé de présenter quelques-unes des caractéristiques (fonctions multiples, création de nouveaux opérateurs, etc.) qui le rendent particulièrement puissant et expressif.
Parmi celles que nous n'avons pas pu décrire faute de place, Perl 6 offre en particulier :
- un nouveau système de programmation orientée objet particulièrement flexible, puissant et expressif, doté de classes, de méthodes et de rôles, la possibilité de créer facilement de nouveaux types, une introspection approfondie et une couche métaobjet permettant de modifier dynamiquement le comportement des objets et des classes ;
- un système d'expressions régulières nettoyé et refondu, rendu plus lisible et modulaire grâce aux regex nommées qui sont en quelque sorte des briques permettant de construire des expressions régulières bien plus puissantes tout en étant plus lisibles, et débouchant sur la création de véritables grammaires permettant l'analyse lexicale et syntaxique non seulement de texte HTML, JSON, XML, etc., mais aussi de langages de programmation : la grammaire utilisée par Perl 6 pour analyser les programmes Perl 6 est elle-même écrite en Perl 6, et il est même possible dans un programme ou un module d'ajouter de nouveaux éléments syntaxiques à la grammaire Perl 6 existante, ce qui rend le langage intrinsèquement malléable et évolutif ;
- un modèle de programmation fonctionnelle très enrichi, avec en natif le support aux listes paresseuses, la programmation en pipe-line, les fonctions d'ordre supérieur, les itérateurs, les hyperopérateurs, les fermetures, les lambdas, la curryfication, etc.
- un support exceptionnellement efficace à l'Unicode, probablement sans égal actuellement ;
- un modèle de programmation parallèle, concurrente et asynchrone de haut niveau, bien plus puissant et expressif que les threads, sémaphores et verrous, fiable, robuste, facile à utiliser et extrêmement prometteur, s'ajoutant à la possibilité d'utilisation implicite par le compilateur de threads utilisant différents cores d'un processeur pour traiter des données en parallèle, ainsi qu'un support natif à la programmation événementielle.
- un support natif à des structures de données multidimensionnelles de plus bas niveau (matrices, etc.) permettant d'envisager du calcul scientifique intensif ;
- un interfaçage particulièrement simple avec des bibliothèques C/C++, Java ou autres, ainsi qu'avec les anciennes versions de Perl, ce qui permet l'utilisation de modules Perl 5 en Perl 6 (ou l'inverse) et ouvre donc la voie à l'utilisation des modules Perl 5 du CPAN, l'une des plus vastes collections de bibliothèques logicielles libres au monde.
La liste ci-dessus pourrait se poursuivre sur plusieurs pages, mais nous nous arrêterons là pour éviter de lasser le lecteur.
Tout cela fait de Perl 6 un langage exceptionnellement expressif et intrinsèquement malléable ; même ce qui n'existe pas dans le langage, vous pouvez presque toujours le créer dynamiquement à la demande.
Tout n'est pas encore parfait pour autant. Les performances d'exécution se sont considérablement améliorées depuis un an, mais elles laissent encore un peu à désirer dans certains cas. Les macros avancées ne sont pas encore implémentées et deux ou trois autres fonctionnalités initialement prévues (comme les entrées/sorties non bloquantes) sont encore incomplètement mises en œuvre. Quelques progrès sont donc encore nécessaires (et font l'objet de travaux intensifs), mais Perl 6 offre d'ores et déjà une palette de fonctionnalités que nous pensons très largement inégalée, même parmi les langages récents bénéficiant des ressources incomparablement plus abondantes d'entreprises richissimes comme Google, Apple, Microsoft, Oracle et quelques autres géants du Nasdaq.
Pour aller plus loin
- Le site de téléchargement de Rakudo Star / Perl 6 : http://rakudo.org/downloads/star/.
- La documentation officielle (en anglais) sur Perl 6 est disponible à l'adresse suivante : doc.perl6.org.
- Plusieurs articles et tutoriels en français sont en ligne sur ce site à l'adresse suivante : https://perl.developpez.com/cours/#TutorielsPerl6.
- Le site des Mongueurs de Perl fournit des liens complémentaires : http://mongueurs.pm/ressources/ref_perl6.html.
- Mon livre sur Perl 6 (en anglais, publié en version papier chez O'Reilly Media et disponible au téléchargement gratuit au format PDF) : Think Perl 6, how to think like a computer scientist.