Skip to content

Latest commit

 

History

History
3007 lines (2258 loc) · 87.4 KB

fr.rakuguide.adoc

File metadata and controls

3007 lines (2258 loc) · 87.4 KB

Introduction à Raku

Table des Matières

L’objectif de ce document est de vous donner un aperçu rapide du langage de programmation Raku.
Pour ceux qui sont nouveaux à Raku, il devrait vous permettre de démarrer.

Certaines sections de ce document référencent d’autres parties (plus complètes et précises) de la documentation de Raku. Vous devriez les lire si vous avez besoin de plus d’information sur un sujet précis.

En lisant ce document, vous trouverez des exemples pour la plus part des sujets discutés. Pour mieux les comprendre, vous pouvez expérimenter en les modifiant.

Licence

Ce document est sous licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International. Pour accéder à une copie de cette licence, merci de vous rendre à l’adresse suivante

Contribution

Si vous souhaitez contribuer à ce document rendez-vous à l’adresse suivante:

1. Introduction

1.1. Raku c’est quoi?

Raku est un langage de haut niveau, générique et dynamique. Il supporte plusieurs paradigmes dont : la programmation procédurale, la programmation orientée objet et la programmation fonctionnelle.

La devise de Raku:
  • TMTOWTDI (prononcé Tim Toady): There is more than one way to do it: c’est-à-dire « Il y a plus d’une façon de le faire »

  • Easy things should stay easy, hard things should get easier, and impossible things should get hard: « Les choses faciles doivent rester faciles, les choses difficiles devraient devenir plus faciles, et les choses impossibles devraient devenir difficiles »

Un programme ou script Raku est un fichier texte qui sera compilé et exécuté par l’exécutable raku ou perl6 et la machine virtuelle associée (par exemple MoarVM ou JVM).

1.2. Jargon

  • Raku est une spécification de langage avec une suite de tests. Les implémentations qui passent la suite de tests sont considérées comme du Raku.

  • Rakudo est un compilateur pour Raku.

  • Rakudobrew est un gestionnaire d’installation pour Rakudo.

  • Zef est un installeur de modules pour Raku.

  • Rakudo Star est un paquet qui comprend : Rakudo, Zef, une collection de modules, et de la documentation.

1.3. Installer Raku

Linux
mkdir ~/rakudo && cd $_
curl -LJO https://rakudo.org/latest/star/src
tar -xzf rakudo-star-*.tar.gz
mv rakudo-star-*/* .
rm -fr rakudo-star-*

./bin/rstar install

echo "export PATH=$(pwd)/bin/:$(pwd)/share/perl6/site/bin:$(pwd)/share/perl6/vendor/bin:$(pwd)/share/perl6/core/bin:\$PATH" >> ~/.bashrc
source ~/.bashrc
macOS

Quatre solutions possibles :

  • Suivez les mêmes étapes que celles indiquées pour l’installation sur Linux

  • Installer avec homebrew: brew install rakudo-star

  • Installez avec MacPorts: sudo port install rakudo

  • Téléchargez l’installateur (fichier avec l’extension .dmg) depuis https://rakudo.org/latest/star/macos

Windows
  1. Pour les architectures 64-bit : Téléchargez le dernier installeur (extension .msi) ici https://rakudo.org/latest/star/win

  2. Après l’installation, assurez vous que C:\rakudo\bin soit dans votre PATH.

Docker
  1. Obtenez l’image officielle docker pull rakudo-star

  2. Ensuite exécutez docker run -it rakudo-star

1.4. Exécuter du code Raku

On peut exécuter du code Raku en utilisant le terminal Raku interactif REPL (Read-Eval-Print Loop).
Pour ce faire, ouvrez un terminal, tapez raku ou perl6 dans la fenêtre de terminal et ensuite la touche [Entrée].
Une invite de commande > apparaîtra.
Ensuite, tapez une ligne de code puis la touche [Entrée]. Le REPL affichera la valeur de la ligne interprétée. Vous pouvez taper une autre ligne, ou exit et ensuite [Entrée] pour sortir du REPL.

L’autre façon consiste à écrire votre code dans un fichier, le sauvegarder puis l’exécuter. Il est conseillé, pour plus de clarté, que les scripts Raku portent l’extension .raku. Exécutez le fichier en entrant raku ou perl6 nom-du-fichier.raku dans la fenêtre de terminal (puis [Entrée]).
À l’inverse du REPL, le résultat ne sera pas automatiquement affiché pour chaque ligne: le code doit contenir une instruction comme say ou print pour afficher une sortie.

Le REPL est la plupart du temps utilisé pour essayer un morceau de code, le plus souvent une seule ligne. Pour des programmes de plus d’une seule ligne, la méthode fichier/exécution est recommandée.

Les lignes de code unilignes peuvent aussi être entrées de façon non interactive sur la ligne de commande en tapant raku -e 'mon code ici' ou perl6 -e 'mon code ici' et ensuite [Entrée].

Tip

Rakudo Star fournit un éditeur ligne par ligne, qui augmente les fonctionnalités du REPL. Comme: le rappel des commandes par les flèches « haut/bas », l’édition avec les flèches « gauche/droite » et la complétion avec la touche [TAB].

Si vous avez seulement installé Rakudo au lieu de Rakudo Star, vous n’aurez probablement pas les fonctions d’édition ligne par ligne. Lancez la commande suivante sur votre terminal pour y avoir accès:

  • zef install Linenoise fonctionne sur Windows, Linux and OS X

  • zef install Readline si vous êtes sur Linux et préférez la bibliothèque Readline

1.5. Editeurs

Comme la plupart du temps, nous allons écrire et stocker nos programmes en Raku dans des fichiers, nous devrions avoir un éditeur de texte décent qui reconnaît la syntaxe de Raku.

Un IDE spécifique à Raku dévellopé par Edument est disponible ici sous le nom de Comma)

D’autres personnes de la communauté utilisent aussi Vim, Emacs, Padre ou Atom.

Les versions récentes de Vim sont livrées avec la coloration syntaxique pour Raku. Emacs et Padre nécessiteront l’installation de paquets supplémentaires.

Tip
N’importe quel éditeur de texte peut être utilisé pour écrire ou lire un programme Raku.

1.6. Bonjour Monde!

Nous allons commencer avec le rituel hello world.

say 'Bonjour Monde';

qui peut aussi s’écrire:

'Bonjour Monde'.say;

1.7. Aperçu de la syntaxe

Raku a une forme libre: vous êtes libre (la plupart du temps) d’utiliser n’importe quelle quantité d’espaces.

Les instructions sont typiquement une ligne logique de code, elles doivent se terminer par un point-virgule:
say "Hello" if True;

Les expressions sont un type spécial d’instructions qui retournent une valeur:
1+2 retourne 3

Les expressions sont faites de termes et d'opérateurs.

Les termes sont des:

  • variables: une valeur qui peut être manipulée ou changée.

  • valeurs littérales: une valeur constante comme un nombre ou une chaîne.

Les opérateurs sont classés en types:

Type

Explication

Exemple

Préfixé

Avant le terme.

++1

Infixé

Entre deux termes.

1+2

Suffixé

Après le terme.

1++

Circonfixé

Autour du terme.

(1)

Postcirconfixé

Après un terme, autour d’un autre

Array[1]

1.7.1. Identificateurs

Les identificateurs sont le nom donné aux termes lors de leur définition.

Règles:
  • ils doivent commencer par un caractère alphabétique ou un tiret bas (underscore).

  • ils peuvent contenir des chiffres (à l’exception du premier caractère).

  • ils peuvent contenir des tirets ou des apostrophes (sauf le premier et le dernier caractère), mais avec un caractère alphabétique à droite de chaque tiret apostrophe.

Valide

Non valide

var1

1var

var-one

var-1

var’one

var'1

var1_

var1'

_var

-var

Conventions de nommage:
  • Camel: variableNo1

  • Kebab: variable-no1

  • Snake: variable_no1

Vous êtes libre de nommer vos identificateurs comme vous le souhaitez, mais, pour des raisons de cohérence et de lisibilité, il est recommandé de choisir une convention de nommage et de s’y tenir.

L’utilisation de noms signifiants facilitera votre vie et celle des autres.
var1 = var2 * var3 est syntaxiquement correct, mais son but n’est pas évident.
salaire-mensuel = salaire-journalier * jours-travaillés serait une meilleure façon de nommer vos variables.

1.7.2. Commentaires

Un commentaire est du texte ignoré par le compilateur.

Il y a 3 types de commentaires:

  • ligne unique:

    #Ceci est une seule ligne de commentaire
  • intégré:

    say #`(Ceci est un commentaire intégré) "Bonjour Monde."
  • multi ligne:

    =begin comment
    Ceci est un commentaire sur plusieurs lignes.
    Commentaire 1
    Commentaire 2
    =end comment

1.7.3. Guillemets

Les chaînes doivent être délimitées par des guillemets droits, doubles ou simples (apostrophes).

Utilisez toujours des guillemets droits doubles:

  • si votre chaîne contient une apostrophe.

  • si votre chaîne contient une variable qui doit être interpolée.

say 'Bonjour Monde';             #Bonjour Monde
say "Bonjour Monde";             #Bonjour Monde
say "Quelqu'un m'a dit";         #Quelqu'un m'a dit
my $nom = 'François Pinon';
say 'Salut $nom';                #Salut $nom
say "Salut $nom";                #Salut François Pinon

2. Opérateurs

Operateur Type Description Exemple Résultat

+

Infixé

Addition

1 + 2

3

-

Infixé

Soustraction

3 - 1

2

*

Infixé

Multiplication

3 * 2

6

**

Infixé

Puissance

3 ** 2

9

/

Infixé

Division

3 / 2

1.5

div

Infixé

Division (arrondit vers le bas)

3 div 2

1

%

Infixé

Modulo (reste de la division entière)

7 % 4

3

%%

Infixé

Divisibilité

6 %% 4

False

6 %% 3

True

gcd

Infixé

Plus grand dénominateur commun

6 gcd 9

3

lcm

Infixé

Plus petit commun multiple

6 lcm 9

18

==

Infixé

Egalité

9 == 7

False

!=

Infixé

Inégalité

9 != 7

True

<

Infixé

Plus petit

9 < 7

False

>

Infixé

Plus grand

9 > 7

True

<=

Infixé

Plus petit ou égal

7 <= 7

True

>=

Infixé

Plus grand ou égal

9 >= 7

True

eq

Infixé

Egalité (chaînes)

"Tintin" eq "Tintin"

True

ne

Infixé

Inégalité (chaînes)

"Tintin" ne "Titine"

True

=

Infixé

Affectation

my $var = 7

Attribue la valeur 7 a la variable $var

~

Infixé

Concaténation

9 ~ 7

97

"Bonjour " ~ "chez vous"

Bonjour chez vous

x

Infixé

Réplication

13 x 3

131313

"Salut " x 3

Salut Salut Salut

~~

Infixé

Smart match (reconnaissance intelligente)

2 ~~ 2

True

2 ~~ Int

True

"Raku" ~~ "Raku"

True

"Raku" ~~ Str

True

"Renaissance" ~~ /naissance/

「naissance」

++

Préfixé

Incrémentation

my $var = 2; ++$var;

Incrémente la variable de 1 et retourne le résultat 3

Suffixé

Incrémentation

my $var = 2; $var++;

Retourne la variable 2 et puis l’incrémente

--

Préfixé

Décrémentation

my $var = 2; --$var;

Décrémente la variable de 1 et retourne le résultat 1

Suffixé

Décrémentation

my $var = 2; $var--;

Retourne la variable 2 et puis la décrémente

+

Préfixé

Force l’opérande a une valeur numérique

+"3"

3

+True

1

+False

0

-

Préfixé

Force l’opérande a une valeur numérique et retourne la négation

-"3"

-3

-True

-1

-False

0

?

Préfixé

Force l’opérande a une valeur booléenne

?0

False

?9.8

True

?"Hello"

True

?""

False

my $var; ?$var;

False

my $var = 7; ?$var;

True

!

Préfixé

Force l’opérande a une valeur booléenne et retourne la négation

!4

False

..

Infixé

Construction d’intervalles

0..5

Crée un intervalle de 0 a 5

..^

Infixé

Construction d’intervalles

0..^5

Crée un intervalle de 0 a 4

^..

Infixé

Construction d’intervalles

0^..5

Crée un intervalle de 1 a 5

^..^

Infixé

Construction d’intervalles

0^..^5

Crée un intervalle de 1 a 4

^

Préfixé

Construction d’intervalles

^5

Comme 0..^5 Crée un intervalle de 0 a 4

…​

Infixé

Construction de listes paresseuses

0…​9999

Retourne les éléments seulement si nécessaire

|

Préfixé

Aplanissement

|(0..5)

(0 1 2 3 4 5)

|(0^..^5)

(1 2 3 4)

Note
Pour la liste complète des opérateurs, y compris leur priorité: https://docs.raku.org/language/operators

3. Variables

Les variables sont classées en trois catégories : scalaires, tableaux et hachages.

Un sigil (signe en Latin) est un caractère utilisé comme préfixe pour classer les variables.

  • $ est utilisé pour les scalaires

  • @ est utilisé pour les tableaux

  • % est utilisé pour les tables de hachage.

3.1. Scalaire

Un scalaire contient une valeur ou une référence.

#String
my $nom = 'François Pinon';
say $nom;

#Integer
my $age = 20;
say $age;

Certaines opérations peuvent être effectuées sur un scalaire, suivant le type de valeur qu’il contient.

Chaîne
my $nom = 'François Pinon';
say $nom.uc;
say $nom.chars;
say $nom.flip;
FRANÇOIS PINON
14
noniP sioçnarF
Note
Pour une liste exhaustive des méthodes applicables aux chaînes, voir https://docs.raku.org/type/Str
Entier
my $age = 17;
say $age.is-prime;
True
Note
Pour une liste exhaustive des méthodes applicables aux entiers, voir https://docs.raku.org/type/Int
Nombre rationnel
my $age = 2.3;
say $age.numerator;
say $age.denominator;
say $age.nude;
23
10
(23 10)
Note
Pour une liste exhaustive des méthodes applicables aux nombres rationnels, voir https://docs.raku.org/type/Rat

3.2. Tableaux

Les tableaux sont des listes contenant plusieurs valeurs.
Par défaut, les valeurs d’un tableau peuvent être de différents types.

my @animaux = 'chameau','lama','hibou';
say @animaux;

De nombreuses opérations peuvent être effectuées sur les tableaux comme le montre l’exemple suivant:

Tip
Le tilde ~ est utilisé pour la concaténation.
Script
my @animaux = 'chameau','vigogne','lama';
say "Le zoo contient " ~ @animaux.elems ~ " animaux";
say "Les animaux sont: " ~ @animaux;
say "Je vais adopter un hibou pour le zoo";
@animaux.push("hibou");
say "Maintenant, mon zoo contient: " ~ @animaux;
say "Le premier animal que nous avons adopté est le " ~ @animaux[0];
@animaux.pop;
say "Malheureusement, le hibou est parti, il ne nous reste que: " ~ @animaux;
say "Nous allons fermer le zoo et laisser un animal seulement";
say "Nous allons faire partir: " ~ @animaux.splice(1,2) ~ " et laisser le " ~ @animaux;
Sortie
Le zoo contient 3 animaux
Les animaux sont: chameau vigogne lama
Je vais adopter un hibou pour le zoo
Maintenant, mon zoo contient: chameau vigogne lama hibou
Le premier animal que nous avons adopté est le chameau
Malheureusement, le hibou est parti, il ne nous reste que: chameau vigogne lama
Nous allons fermer le zoo et laisser un animal seulement
Nous allons faire partir: vigogne lama et laisser le chameau
Explication

.elems retourne le nombre d’éléments contenus dans le tableau.
.push() ajoute un élément au tableau.
Nous pouvons accéder à un élément spécifique dans le tableau en spécifiant sa position @animaux[0].
.pop supprime le dernier élément du tableau.
.splice(a,b) supprime les b éléments à partir de la position a.

3.2.1. Tableaux de taille fixe

Un tableau simple se déclare comme ceci:

my @tableau;

Le tableau simple à une taille non définie, et peut varier de façon automatique.
Ce tableau acceptera un nombre illimité de valeurs sans restriction.

On peut en revanche créer des tableaux de taille fixe.
Ces tableaux ne pourront pas excéder la taille qui leur aura été allouée (en lecture et écriture).

Pour déclarer un tableau de taille fixe, spécifiez son nombre maximal d’éléments entre crochets à la suite de son nom:

my @tableau[3];

Ce tableau pourra contenir un maximum de trois valeurs, indexées de 0 à 2.

my @tableau[3];
@tableau[0] = "première valeur";
@tableau[1] = "deuxième valeur";
@tableau[2] = "troisième valeur";

Vous ne pourrez pas ajouter une quatrième valeur à ce tableau:

my @tableau[3];
@tableau[0] = "première valeur";
@tableau[1] = "deuxième valeur";
@tableau[2] = "troisième valeur";
@tableau[3] = "quatrième valeur";
Index 3 for dimension 1 out of range (must be 0..2)

3.2.2. Tableaux à plusieurs dimmensions

Les tableaux vus précédemment ne sont qu’à une dimension.
Heureusement, nous pouvons en Raku déclarer des tableaux de dimensions multiples.

my @multi-tab[3;2];

Ce tableau a deux dimensions. La première dimension peut contenir un maximum de trois valeurs et la seconde un maximum de deux valeurs.

my @multi-tab[3;2];
@multi-tab[0;0] = 1;
@multi-tab[0;1] = "x";
@multi-tab[1;0] = 2;
@multi-tab[1;1] = "y";
@multi-tab[2;0] = 3;
@multi-tab[2;1] = "z";
say @multi-tab
[[1 x] [2 y] [3 z]]
Note
Pour la référence complète des tableaux: https://docs.raku.org/type/Array

3.3. Hachage

Un hachage (table de hachage / hash) est un ensemble de paires clef / valeur.
my %capitales = ('Angleterre','Londres','France','Paris');
say %capitales;
Une autre façon succincte de remplir le hachage:
my %capitales = (Angleterre => 'Londres', France => 'Paris');
say %capitales;

Voici quelques-unes des méthodes qui peuvent être appelées sur les hachages:

Script
my %capitales = (Angleterre => 'Londres', Allemagne => 'Berlin');
%capitales.push: (France => 'Paris');
say %capitales;
say %capitales.kv;
say %capitales.keys;
say %capitales.values;
say "La capitale de la France est: " ~ %capitales<France>;
Sortie
{Allemagne => Berlin, Angleterre => Londres, France => Paris}
(France Paris Allemagne Berlin Angleterre Londres)
(France Allemagne Angleterre)
(Paris Berlin Londres)
La capitale de la France est: Paris
Explication

.push: (clef => 'valeur') ajoute une nouvelle paire clef/valeur.
.kv renvoie la liste contenant toutes les clefs et valeurs.
.keys renvoie une liste des clefs.
.values renvoie une liste des valeurs.
On peut accéder à la valeur particulière d’un hachage en spécifiant sa clef, comme suit: %hachage<clef>

Note
Pour la référence complète des hachages: https://docs.raku.org/type/Hash

3.4. Types

Dans les exemples précédents, on n’a pas précisé quel type de valeurs les variables peuvent contenir.

Tip
.WHAT retournera le type de la valeur contenue dans la variable.
my $var = 'Texte';
say $var;
say $var.WHAT;

$var = 123;
say $var;
say $var.WHAT;

Comme vous pouvez le voir dans l’exemple ci-dessus, le type de valeur contenu dans $var était (Str) et puis (Int).

Ce style de programmation est appelé le typage dynamique. Dynamique dans le sens que les variables peuvent contenir des valeurs de tout type.

Maintenant, essayez d’exécuter l’exemple ci-dessous:
Remarquez Int avant le nom de la variable.

my Int $var = 'Texte';
say $var;
say $var.WHAT;

Il va échouer et retourner ce message d’erreur: Type check failed in assignment to $var; expected Int but got Str

Ce qui est arrivé est que nous avons précisé au préalable que la variable doit être de type (Int). Quand nous avons essayé de lui affecter un (Str), le programme a échoué.

Ce style de programmation est appelé le typage statique. Statique dans le sens que les types de variables sont définis avant l’affectation et ne peuvent pas changer.

Raku possède un typage graduel, les deux typages, statique et dynamique, peuvent être utilisés.

Voici une liste des types les plus couramment utilisés.

Les deux premiers ne seront probablement jamais utilisés, mais ils sont répertoriés à titre informatif.

Type

Description

Exemple

Résultat

Mu

La racine de la hiérarchie de types

Any

Classe de base par défaut pour les nouvelles classes et pour la plupart des classes intégrées

Cool

Valeur qui peut être considérée comme une chaîne ou un nombre interchangeable

my Cool $var = 31; say $var.flip; say $var * 2;

13 62

Str

Chaîne de caractères

my Str $var = "NEON"; say $var.flip;

NOEN

Int

Entier (précision arbitraire)

7 + 7

14

Rat

Nombre rationnel (précision limitée)

0.1 + 0.2

0.3

Bool

Booléen

!True

False

3.5. Introspection

L’introspection est le processus d’obtention d’informations sur les propriétés d’un objet comme son type.
Dans l’exemple précédent, nous avons utilisé .WHAT pour connaître le type de la variable.

my Int $var;
say $var.WHAT;    # (Int)
my $var2;
say $var2.WHAT;   # (Any)
$var2 = 1;
say $var2.WHAT;   # (Int)
$var2 = "Hello";
say $var2.WHAT;   # (Str)
$var2 = True;
say $var2.WHAT;   # (Bool)
$var2 = Nil;
say $var2.WHAT;   # (Any)

Le type d’une variable contenant une valeur est corrélé à sa valeur.
Le type d’une variable vide fortement déclarée est le type avec lequel elle a été déclarée.
Le type d’une variable vide qui n’a pas été déclarée fortement est (Any)
Pour vider la valeur d’une variable, vous pouvez lui affecter Nil.

3.6. Portée

Avant d’utiliser une variable pour la première fois, elle doit être déclarée.

Plusieurs déclarateurs peuvent être utilisés dans Raku, my est ce que nous avons utilisé jusqu’ici.

my $var=1;

Le déclarateur my donne à la variable une portée lexicale. En d’autres termes, la variable ne sera accessible que dans le bloc où elle a été déclarée.

Un bloc en Raku est délimité par { }. Si aucun bloc n’est trouvé, la variable sera disponible dans l’ensemble du script (on dit alors parfois qu’elle est globale au script).

{
  my Str $var = 'Texte';
  say $var; #accessible
}
say $var; #inaccessible, renvoie une erreur

Comme une variable est uniquement accessible dans le bloc où elle est définie, le même nom de variable peut être redéfini dans un autre bloc.

{
  my Str $var = 'Texte';
  say $var;
}
my Int $var = 123;
say $var;

3.7. Affecter vs. Lier

Nous avons vu dans les exemples précédents comment affecter des valeurs aux variables.
L'affectation est faite en utilisant l’opérateur =

my Int $var = 123;
say $var;

Nous pouvons modifier la valeur attribuée à une variable:

Affecter
my Int $var = 123;
say $var;
$var = 999;
say $var;
Sortie
123
999

D’autre part, nous ne pouvons pas changer la valeur liée à une variable.
Le lien est établi en utilisant l’opérateur :=

Lier
my Int $var := 123;
say $var;
$var = 999;
say $var;
Sortie
123
Cannot assign to an immutable value
Une variable peut être également liée a une autre:
my $a;
my $b := $a;
$a = 7;
say $b;

Un lien ne peut être créé que lors de l’initialisation de la variable liée, et ne peut plus être modifié ensuite. Mais la valeur de la variable liée peut néanmoins changer si la valeur de la variable « maîtresse » à laquelle elle est liée change.

Note
Pour plus d’informations sur les variables, rendez-vous à https://docs.raku.org/language/variables

4. Fonctions normales et fonctions mutatrices

Il est important de différencier entre les fonctions normales et les fonctions mutatrices.
Les fonctions normales ne changent pas l’état initial de l’objet.
Les fonctions mutatrices modifient l’état de l’objet.

Script
my @numeros = [7,2,4,9,11,3];

@numeros.push(99);
say @numeros;      #1

say @numeros.sort; #2
say @numeros;      #3

@numeros.=sort;
say @numeros;      #4
Sortie
[7 2 4 9 11 3 99] #1
(2 3 4 7 9 11 99) #2
[7 2 4 9 11 3 99] #3
[2 3 4 7 9 11 99] #4
Explication

.push est une fonction mutatrice, elle change l’état du tableau (#1)

.sort est une fonction normale, elle retourne un tableau trié, mais ne modifie pas l’état initial du tableau:

  • (#2) démontre le retour d’un tableau trié

  • (#3) démontre que le tableau initial reste non modifié.

Afin de forcer une fonction normale à agir comme une fonction mutatrice, nous pouvons utiliser .= a la place de . (#4) (Ligne 9 du script)

5. Structures conditionnelles et boucles

Raku possède une multitude de structures conditionnelles et structures de boucles.

5.1. if

Le code ne s’exécute que si la condition a été remplie.

my $âge = 19;

if $âge > 18 {
  say 'Bienvenue'
}

En Raku, nous pouvons inverser le code et la condition.
Même si le code et la condition ont été inversés, la condition est toujours évaluée en premier.

my $âge = 19;

say 'Bienvenue' if $âge > 18;

Si la condition n’est pas remplie, nous pouvons toujours préciser des blocs d’exécution alternatifs en utilisant:

  • else

  • elsif

#exécuter le même code pour différentes valeurs de la variable
my $nombre-de-places = 9;

if $nombre-de-places <= 5 {
  say 'Je suis une berline'
} elsif $nombre-de-places <= 7 {
  say 'Je suis un monospace'
} else {
  say 'Je suis un van'
}

5.2. unless

La version négative d’un if peut être écrite en utilisant unless.

Le code suivant:

my $chaussures-propres = False;

if not $chaussures-propres {
  say 'Nettoyez vos chaussures'
}

peut aussi être écrit ainsi:

my $chaussures-propres = False;

unless $chaussures-propres {
  say 'Nettoyez vos chaussures'
}

La négation en Raku est faite en utilisant ! ou not.

unless (condition) est utilisé à la place de if not (condition).

unless ne peux pas avoir une clause else.

5.3. with

with fonctionne comme if, mais vérifie si la variable est définie.

my Int $var=1;

with $var {
  say 'Bonjour'
}

Si vous exécutez le code sans attribuer une valeur à la variable, rien ne devrait arriver.

my Int $var;

with $var {
  say 'Bonjour'
}

without est la version négative de with. Vous devriez être capable de le relier le concept à unless.

Si la première condition with n’est pas remplie, un autre chemin peut être spécifié en utilisant orwith.
with et orwith peuvent être comparés à if et elsif.

5.4. for

La boucle for itère sur plusieurs valeurs.

my @tableau = 1,2,3;

for @tableau -> $element {
  say $element*100
}

Notez que nous avons créé une variable d’itération $element afin d’effectuer l’opération *100 sur chaque élément du tableau. Dans ce genre de construction, la variable d’itération $element est autodéclarée et ne doit donc pas être précédée par le déclarateur my.

5.5. given

given est l’équivalent Raku de l’instruction switch dans d’autres langages.

my $var = 42;

given $var {
    when 0..50 { say 'Plus petit que 50'}
    when Int { say "est un Int" }
    when 42  { say 42 }
    default  { say "huh?" }
}

Si l’une des conditions est satisfaite, le processus d’appariement s’arrête (les autres conditions ne seront pas testées). Le code ci-dessus n’affichera donc que Plus petit que 50.

Si l’on préfère tester aussi les conditions suivantes, proceed instruira Raku à poursuivre l’appariement, même après un appariement réussi.

my $var = 42;

given $var {
    when 0..50 { say 'Plus petit que 50';proceed}
    when Int { say "est un Int";proceed}
    when 42  { say 42 }
    default  { say "huh?" }
}

5.6. loop

loop est une autre façon d’écrire une boucle for.

En fait loop s’écrit comme le sont les boucles for dans les langages de programmation appartenant a la famille C.

Raku appartient à la famille C.

loop (my $i=0; $i < 5; $i++) {
  say "Le nombre actuel est $i"
}
Note
Pour plus d’informations sur les boucles et les conditions, voir https://docs.raku.org/language/control

6. Entrées/Sorties

En Raku, deux des interfaces entrée/sortie les plus communes sont le Terminal et les Fichiers.

6.1. E/S Basic en utilisant le Terminal

6.1.1. say

say écrit sur la sortie standard (en général, l’écran). Il ajoute un caractère de fin ligne à la fin. Autrement dit, le code suivant:

say 'Bonjour Madame.';
say 'Bonjour Monsieur.';

sera écrit sur 2 lignes distinctes.

6.1.2. print

print fonctionne comme say, mais sans ajouter de caractère de fin ligne.

Essayez de remplacer say avec print et de comparer les deux résultats.

6.1.3. get

get est utilisé pour capturer l’entrée du Terminal.

my $nom;

say "Salut quel est ton nom?";
$nom=get;

say "Cher $nom bienvenue à Raku";

Lorsque le code ci-dessus est lancé, le terminal attendra que vous saisissiez votre nom. Par la suite, il vous accueillera.

6.1.4. prompt

prompt est une combinaison de print et get.

L’exemple ci-dessus peut être écrit comme ceci:

my $nom = prompt("Salut quel est ton nom? ");

say "Cher $nom bienvenue à Raku";

6.2. Exécution de commandes Shell

Deux routines peuvent être utilisées pour exécuter des commandes shell:

  • run exécute une commande externe sans impliquer le shell

  • shell exécute une commande via le shell. Tous les métacaractères sont interprétés par le shell, y compris les tubes (pipes), les redirections, les variables d’environnement, etc.

Voici un exemple sous Linux, Unix ou OS X:
my $nom = 'Neo';
my $commande = run 'echo', "salut $nom";
my $commande2 = shell "ls";
Et un exemple sous Windows:
shell "dir";

echo et ls sont des mots-clefs communs des shells Unix ou Linux.
echo imprime le texte sur le terminal (l’équivalent de say en Raku)
ls liste tous les fichiers et dossiers dans le répertoire courant sous Linux et dir fait la même chose sous Windows.

6.3. E/S Fichier

6.3.1. slurp

slurp est utilisé pour lire les données d’un fichier.

Créez un fichier texte avec le contenu suivant:

datafile.txt
John 9
Johnnie 7
Jane 8
Joanna 7
my $data = slurp "datafile.txt";
say $data;

6.3.2. spurt

spurt est utilisé pour écrire des données sur un fichier.

my $newdata = "New scores:
Paul 10
Paulie 9
Paulo 11";

spurt "newdatafile.txt", $newdata;

Après avoir exécuté le code ci-dessus, un nouveau fichier nommé newdatafile.txt sera créé. Il contiendra les nouveaux scores.

6.4. Travailler avec les fichiers et répertoires

Raku peut lister le contenu d’un répertoire sans exécuter des commandes shell (en utilisant ls) comme nous l’avons vu dans un exemple précédent.

say dir;              #Liste les fichiers et dossiers dans le répertoire courant
say dir "/Documents"; #Liste les fichiers et dossiers dans le répertoire spécifié
my @répertoire = dir; # Récupère les fichiers dans un tableau

De plus, vous pouvez créer de nouveaux dossiers et les supprimer.

mkdir "newfolder";
rmdir "newfolder";

mkdir crée un nouveau répertoire.
rmdir supprimer un répertoire vide. Renvoie une erreur s’il n’est pas vide.

Vous pouvez également vérifier si le chemin d’accès spécifié existe, si c’est un fichier ou un répertoire.

Dans le répertoire où vous allez exécuter le script ci-dessous, créez un dossier vide folder123 et un fichier Raku vide script123.raku

say "script123.raku".IO.e;
say "folder123".IO.e;

say "script123.raku".IO.d;
say "folder123".IO.d;

say "script123.raku".IO.f;
say "folder123".IO.f;
Note
La méthode IO sert à transformer la chaîne de caractères « script123 » en un objet de type IO::Path. Les méthodes « e », « f » et « d » de tests de fichiers ne peuvent être invoquées que sur des objets de type IO::Path, d’où la nécessité de coercition préalable de la chaîne de caractères en un objet de ce type.

IO.e vérifie si le répertoire/fichier existe.
IO.f vérifie si c’est un fichier.
IO.d vérifie si c’est un dossier.

Warning
Les utilisateurs Windows peuvent utiliser / ou \\ comme séparateur entre les dossiers:
C:\\rakudo\\bin
C:/rakudo/bin
Note
Pour plus d’informations sur les E/S, voir https://docs.raku.org/type/IO

7. Routines

7.1. Définition

Les routines ou subroutines ou subs sont un moyen de conditionnement d’un ensemble de fonctionnalités.

Une routine est définie avec le mot-clef sub. Après leur définition, elles peuvent être appelées par leur nom.
Examinez l’exemple ci-dessous:

sub salut-alien {
  say "Bonjour Terriens";
}

salut-alien;

L’exemple précédent présente une routine qui ne nécessite aucun argument.

7.2. Signature

Beaucoup de routines requièrent des données en entrée pour fonctionner. Ces données sont fournies par des arguments.
La signature est le nombre et le type d’arguments que la routine accepte.

La routine ci-dessous accepte une chaîne pour argument:

sub dis-bonjour (Str $nom) {
    say "Bonjour " ~ $nom ~ "!!!!"
}
dis-bonjour "Paul";
dis-bonjour "Paula";

7.3. Multiroutines

Il est possible de définir plusieurs routines ayant le même nom, mais des signatures différentes. Lorsque la routine est appelée, l’environnement d’exécution décidera quelle version utiliser en fonction du nombre et du type des arguments fournis. Ce type de routines est défini de la même manière que les routines normales sauf que nous utilisons le mot-clef multi a la place de sub.

multi salut($nom) {
    say "Bonne Journée $nom";
}
multi salut($nom, $titre) {
    say "Bonne Journée $titre $nom";
}

salut "Gaspard";
salut "Josiane","Mme.";

7.4. Arguments optionnels et par défaut

Si une routine est définie comme acceptant un argument, et nous l’appelons sans fournir l’argument requis, la routine va échouer.

Cependant, Raku nous offre la possibilité de définir des routines avec des:

  • Arguments optionnels

  • Arguments par défaut

Les arguments optionnels sont définis en ajoutant ? après le nom de l’argument.

sub dis-bonjour($nom?) {
  with $nom { say "Bonjour " ~ $nom }
  else { say "Bonjour être humain" }
}
dis-bonjour;
dis-bonjour("Laura");

Si l’utilisateur ne fournit pas un argument, la routine peut fournir une valeur par défaut.
Cela se fait par l’attribution d’une valeur à l’argument durant la définition de la routine.

sub dis-bonjour($nom="Raoul") {
  say "Bonjour " ~ $nom;
}
dis-bonjour;
dis-bonjour("Laura");
Note
Pour plus d’informations sur les routines et fonctions, voir https://docs.raku.org/language/functions

8. Programmation fonctionnelle

Ce chapitre traitera des fonctionnalités facilitant la programmation fonctionnelle.

8.1. Les fonctions sont des entités de première classe

Les fonctions/routines sont des entités de première classe:

  • elles peuvent être passées comme un argument

  • elles peuvent être renvoyées par une fonction

  • on peut les affecter à une variable

Un bon exemple pour vérifier ce concept est la fonction map.
map est une fonction d’ordre supérieur, elle accepte une autre fonction comme argument.

Script
my @tableau = <1 2 3 4 5>;
sub carré($x) {
  $x ** 2
}
say map(&carré,@tableau);
Sortie
(1 4 9 16 25)
Explication

Nous avons défini une routine appelée carré, qui met à la puissance 2 l’argument qui lui est passé.
Ensuite, nous avons utilisé map, une fonction d’ordre supérieur en lui passant deux arguments, une routine et un tableau.
Le résultat est une liste de tous les éléments du tableau mis à la puissance 2.

Notez que quand une routine est passée comme argument, nous la préfixons avec &.

8.2. Fermeture

Tous les objets code en Raku sont des fermetures, ce qui implique qu’ils peuvent référencer des variables lexicales d’une portée externe.

8.3. Fonctions anonymes

Une fonction anonyme est également appelée lambda.
Une fonction anonyme n’est pas liée à un identifiant (elle n’a pas de nom).

Réécrivons l’exemple de map avec une fonction anonyme:
my @tableau = <1 2 3 4 5>;
say map(-> $x {$x ** 2},@tableau);

Notez qu’au lieu de définir une routine et de la passer en argument à map, nous la définissons directement à l’intérieur de map.
La routine anonyme -> $x {$x ** 2} n’a pas de nom et ne peut donc pas être appelée.

En dialecte Raku nous l’appelons un bloc pointu (pointy block).

Un bloc pointu peut aussi être utilisé pour assigner des fonctions à des variables:
my $carré = -> $x {
  $x ** 2
}
say $carré(9);

8.4. Enchaînement

En Raku, les méthodes peuvent être enchaînées, vous n’avez plus à passer le résultat d’une méthode comme argument à une autre.

Supposons qu’on vous donne un tableau de valeurs. On vous demande de retourner les valeurs uniques de ce tableau en ordre décroissant.

Vous pouvez résoudre ce problème en écrivant quelque chose comme ceci:

my @tableau = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @tableau-final = reverse(sort(unique(@tableau)));
say @tableau-final;

Nous appelons d’abord la fonction unique sur @tableau puis nous passons le résultat comme argument à sort et ensuite passons le résultat à reverse.

L’exemple ci-dessus peut aussi être écrit comme suit, en prennant avantage de l’enchaînement des méthodes:

my @tableau = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @tableau-final = @tableau.unique.sort.reverse;
say @tableau-final;

Vous pouvez constater qu’enchaîner les méthodes est plus agréable à l’oeil et au cerveau.

8.5. Opérateur feed

L’opérateur feed, appelé Pipe dans d’autres langages fonctionnels, donne une meilleure vue de l’enchaînement de méthodes.

Feed vers l’avant
my @tableau = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
@tableau ==> unique()
         ==> sort()
         ==> reverse()
         ==> my @tableau-final;
say @tableau-final;
Explication
Commence avec `@tableau` puis renvoie la liste des éléments uniques
                         puis effectue un tri
                         puis l'inverse
                         puis stocke le résultat dans @tableau-final

Comme vous le voyez, le flux des appels de méthodes se fait de haut en bas.

Feed vers l’arrière
my @tableau = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @tableau-final-v2 <== reverse()
                     <== sort()
                     <== unique()
                     <== @tableau;
say @tableau-final-v2;
Explication

Le feed vers l’arrière est comme celui vers l’avant, mais se fait à rebours.
Le flux des appels de méthodes se fait de bas en haut.

8.6. Hyperopérateur

L' hyperpérateur >>. invoque une méthode sur tous les éléments d’une liste et renvoie une liste des résultats.

my @tableau = <0 1 2 3 4 5 6 7 8 9 10>;
sub est-pair($var) { $var %% 2 };

say @tableau>>.is-prime;
say @tableau>>.&est-pair;

En utilisant l’hyperopérateur, nous pouvons appeler des méthodes déjà définies dans Raku, par exemple is-prime qui nous indique si un nombre est premier ou pas.
Nous pouvons également définir de nouvelles routines et les appeler en utilisant l’hyperopérateur. En ce cas, il faut préfixer la méthode avec & par exemple &est-pair

Cette façon de faire est très pratique, car elle nous évite d’écrire une boucle for d’itéreration sur chaque valeur.

8.7. Jonctions

Une jonction est une superposition logique des valeurs.

Dans l’exemple ci-dessous, 1|2|3 est une jonction.

my $var = 2;
if $var == 1|2|3 {
  say "La variable est soit 1 ou 2 ou 3";
}

L’utilisation de jonctions déclenche généralement l'autothreading; l’opération est effectuée pour chaque élément de la jonction, les résultats sont combinés en une seule jonction et renvoyés.

8.8. Listes paresseuses

Une liste paresseuse est une liste dont l’évaluation peut être retardée.
L’évaluation paresseuse diffère l’évaluation d’une expression jusqu’au momment où celle-ci est necessaire, et evite ainsi la répétition des évaluations en stockant les résultats dans une table de correspondance.

Les avantages, parmi d’autres, sont les suivants:

  • Un gain de performance évitant les calculs inutiles

  • La possibilité de construire des structures de données potentiellement infinies

  • La possibilité de définir une structure de contrôle

Pour construire une liste paresseuse on utilise l’opérateur infixé …​
Une liste paresseuse possède un ou des éléments initiaux, un générateur, et un élément final.

Liste paresseuse simple
my $lazylist = (1 ... 10);
say $lazylist;

L’élément initial est 1, et l’élément final est 10. Aucun générateur n’a été défini donc le générateur par défaut se fait par sucession (+1)
Autrement dit, cette liste paresseuse retournera (à la demande) les éléments suivants: (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Liste paresseuse infinie
my $lazylist = (1 ... Inf);
say $lazylist;

Cette liste retournera (à la demande) les entiers entre 1 et l’infini, c’est-à-dire tous les entiers.

Liste paresseuse utilisant un générateur déduit
my $lazylist = (0,2 ... 10);
say $lazylist;

Les éléments initiaux sont 0 et 2, et le point final est 10. Aucun générateur n’est défini, mais en utilisant les éléments initiaux, Raku déduira que le générateur est (+2)
Cette liste paresseuse retournera (à la demande) les éléments suivants: (0, 2, 4, 6, 8, 10)

Liste paresseuse utilisant un générateur défini
my $lazylist = (0, { $_ + 3 } ... 12);
say $lazylist;

Dans cet exemple, nous définissons explicitement un générateur mis entre { }
Cette liste paresseuse retournera (à la demande) les éléments suivants: (0, 3, 6, 9, 12)

Warning

Si vous utilisez un générateur explicite, l’élément final doit être une valeur que le générateur puisse retourner.
Si nous reproduisons l’exemple ci-dessus avec un élément final égal à 10 au lieu de 12, il n’y aura pas de fin. Le générateur saute par dessus l’élément final.

Vous pouvez sinon remplacer 0 …​ 10 par 0 …​^ * > 10
Ce qui se lit comme: de 0 jusqu’a la première valeur suppérieure à 10 exclue.

Ceci ne stopera pas le générateur
my $lazylist = (0, { $_ + 3 } ... 10);
say $lazylist;
Ceci stopera le générateur
my $lazylist = (0, { $_ + 3 } ...^ * > 10);
say $lazylist;

9. Classes & Objets

Dans le chapitre précédent nous avons vu comment Raku facilite la programmation fonctionnelle.
Dans ce chapitre nous allons aborder la programmation orientée objet en Raku.

9.1. Introduction

La programmation Orientée Objet est l’un des paradigmes les plus utilisés de nos jours.
Un objet est une collection de variables et routines empaquetées ensemble.
Les variables sont appelées des attributs et les routines des méthodes.
Les attributs définissent l'état et les méthodes le comportement d’un objet.

Une classe définit la structure d’une collection d'objets.

Pour comprendre cette relation, examinez l’exemple ci-dessous:

Il y a 4 personnes présentes dans une pièce

objets ⇒ 4 personnes

Ces 4 personnes sont des êtres humains

classe ⇒ Humain

Ils ont des noms, ages, sexes et nationalités différents

attributs ⇒ nom, age, sexe, nationalité

Dans le jargon orienté objet, nous disons que les objets sont des instances d’une classe.

Voyez le script ci-dessous:

class Humain {
  has $nom;
  has $age;
  has $sexe;
  has $nationalité;
}

my $françois;
$françois = Humain.new(nom => 'François', age => 23, sexe => 'M', nationalité => 'Sarthoise');
say $françois;

Le mot-clef class est utilisé pour définir une classe.
Le mot-clef has est utilisé pour définir un attribut d’une classe.
La méthode .new() est appelée un constructeur. Elle crée l’objet comme une instance de la classe sur laquelle elle a été appelée.

Dans le script ci-dessus, la nouvelle variable $françois contient une référence vers une nouvelle instance de "Humain" définie par Humain.new().
Les arguments passés à la méthode .new() sont utilisés pour initialiser les attributs de l’objet.

Une classe peut se voir donner une portée lexicale en utilisant my:

my class Humain {

}

9.2. Encapsulation

L’encapsulation est un concept orienté objet qui rassemble une collection de données et de méthodes.
Les données (attributs) dans un objet se doivent d’être privées, c’est-à-dire qu’elles ne sont accessibles qu’à l’intérieur de l’objet.
Pour avoir accès aux attributs depuis l’extérieur de l’objet, on utilise des méthodes appelées accesseurs.

Les deux scripts ci-dessous ont le même résultat.

Accès direct à la variable:
my $var = 7;
say $var;
Encapsulation:
my $var = 7;
sub disvar {
  $var;
}
say disvar;

La méthode disvar est un accesseur. Elle nous permet d’avoir accès à la valeur de la variable sans avoir d’accès direct à celle-ci.

L’encapsulation est facilitée en Raku par l’emploi de twigils.
Les twigils sont des sigils secondaires. Ils s’intercalent entre le sigil et le nom de l’attribut.
On utilise 2 twigils dans les classes:

  • ! est utilisé pour déclarer explicitement qu’un attribut est privé.

  • . est utilisé pour générer automatiquement un accesseur pour un attribut.

Par défaut, tout les attributs sont privés, mais c’est une saine habitude que d’utiliser le twigil !.

En accord avec ce qui vient d’être dit, nous devrions réécrire la classe comme suit:

class Humain {
  has $!nom;
  has $!age;
  has $!sexe;
  has $!nationalité;
}

my $françois = Humain.new(nom => 'François', age => 23, sexe => 'M', nationalité => 'Sarthoise');
say $françois;

Si vous ajoutez à ce script la ligne suivante: say $françois.age;
L’erreur suivante sera retournée: Method 'age' not found for invocant of class 'Humain'
La raison en est que $!age est privé et ne peut être utilisé que dans l’objet. Y accéder en dehors de l’objet retournera une erreur.

Maintenant remplacez has $!age par has $.age et voyez quel est le résultat de say $françois.age;

9.3. Arguments nommés vs. positionnels

En Raku, toutes les classes héritent d’un constructeur par défaut .new().
Il peut être utilisé pour créer des objets en leur fournissant des arguments.
Le constructeur par défaut ne peut se voir fournir que des arguments nommés.
Si vous regardez l’exemple ci-dessus, vous voyez que les arguments passés à .new() sont définis par leur noms:

  • nom => 'François'

  • age => 23

Et si je ne veux pas passer le nom de chaque attribut à chaque fois que je crée un nouvel objet?
Je dois alors créer un autre constructeur qui accepte les arguments positionnels.

class Humain {
  has $.nom;
  has $.age;
  has $.sexe;
  has $.nationalité;
  #nouveau constructeur qui prime sur constructeur par défaut.
  method new ($nom,$age,$sexe,$nationalité) {
    self.bless(:$nom,:$age,:$sexe,:$nationalité);
  }
}

my $françois = Humain.new('François',23,'M','Sarthoise');
say $françois;

Le constructeur qui accepte des arguments positionnels doit être défini comme ci-dessus.

9.4. Méthodes

9.4.1. Introduction

Les méthodes sont les routines d’un objet.
Comme les routines, elles sont un moyen d’empaqueter une collection de fonctionnalités, elles acceptent des arguments, ont une signature et peuvent être définies comme multi.

Les méthodes sont définies en utilisant le mot-clef method.
Dans leur usage courant, les méthodes sont requises pour effectuer une action sur les attributs des objets. Ce qui applique l’idée d’encapsulation. Les attributs des objets ne peuvent être manipulés qu’à l’intérieur de l’objet en utilisant des méthodes. L’environement extérieur ne peut interargir avec l’objet qu’à travers ses méthodes, et n’a pas accès à ses attributs.

class Humain {
  has $.nom;
  has $.age;
  has $.sexe;
  has $.nationalité;
  has $.éligible;
  method confirme-éligibilité {
      if self.age < 21 {
        $!éligible = 'Non'
      } else {
        $!éligible = 'Oui'
      }
  }

}

my $françois = Humain.new(nom => 'John', age => 23, sexe => 'M', nationalité => 'Sarthoise');
$françois.confirme-éligibilité;
say $françois.éligible;

Une fois les méthodes définies dans une classe, elles peuvent être appelées sur un objet en utilisant l’opérateur point:
objet . méthode ou comme dans l’exemple ci-dessus: $françois.confirme-éligibilité

Dans la définition d’une méthode, si nous avons besoin de faire référence à l’objet en soi pour appeler une autre méthode, on utilise le mot-clef self.

Dans la définition d’une méthode, si nous avons besoin de faire référence à un attribut, on utilise ! même si il a été définit avec .
La raison en est que le twigil . déclare un attribut privé (avec !) et automatise ensuite la création d’un accesseur ayant le même nom.

Dans l’exemple ci-dessus if self.age < 21 et if $!age < 21 auraient le même résultat, bien qu’ils soient techniquement différents:

  • self.age appelle la méthode .age (accesseur)
    On peut aussi l’écrire $.age

  • $!age est un appel direct à la variable.

9.4.2. Méthodes privées

Les méthodes normales peuvent être appelées sur des objets depuis l’extérieur de la classe.

Les méthodes privées sont des méthodes qui ne peuvent être appelées qu’à l’intérieur de la classe.
Un cas possible d’usage serait une méthode qui en appelle une autre pour une action spécifique. La méthode qui communique avec l’extérieur est publique alors que celle qui est référencée doit rester privée. Nous ne voulons par que les utilisateurs l’appellent directement, on la déclare donc comme privée.

La déclaration d’une méthode privée nécessite l’emploi du twigil ! avant son nom.
Les méthodes privées sont appelées avec ! à la place de .

method !jesuisprivée {
  #du code ici
}

method jesuispublique {
  self!jesuisprivée;
  #faire d'autres choses
}

9.5. Attributs de classe

Les attributs de classe sont des attributs qui appartiennent à la classe, mais pas à ses objets.
Ils peuvent être initialisés pendant la définition.
Les attributs de classe sont déclarés en utilisant my au lieu de has.
Ils sont appelés sur la classe elle-même au lieu de ses objets.

class Humain {
  has $.nom;
  my $.compteur = 0;
  method new($nom) {
    Humain.compteur++;
    self.bless(:$nom);
  }
}
my $a = Humain.new('a');
my $b = Humain.new('b');

say Humain.compteur;

9.6. Types d’accès

Jusqu’à présent, tous les exemples que nous avons vus utilisent des accesseurs pour obtenir des informations sur les attributs de l’objet.

Et si nous avons besoin de modifier la valeur d’un attribut, nous devons marquer cet attribut comme lisible/enregistrable en utilisant le mot-clef is rw

class Humain {
  has $.nom;
  has $.age is rw;
}
my $françois = Humain.new(nom => 'François', age => 21);
say $françois.age;

$françois.age = 23;
say $françois.age;

Par défaut, tous les attribut sont déclarés en lecture seule mais vous pouvez aussi les déclarer explicitement avec is readonly

9.7. Héritage

9.7.1. Introduction

L'héritage est un autre concept de la programmation orientée objet.

Quand on définit des classes, on se rend compte que ces classes ont certains attributs/méthodes communs.
Devons nous dupliquer le code?
NON! Il faudrait utiliser l'héritage.

Disons que nous voulons créer deux classes, une pour les humains et une pour les employés.
Les humains ont deux attributs: nom et âge.
Les employés ont quatre attributs: nom, âge, société et salaire.

On pourrait être tempté de définir les classes comme suit:

class Humain {
  has $.nom;
  has $.age;
}

class Employé {
  has $.nom;
  has $.age;
  has $.société;
  has $.salaire;
}

Bien que correcte, la part de code ci-dessus est conceptuellement maladroite.

Une meilleure approche serait:

class Humain {
  has $.nom;
  has $.age;
}

class Employé is Humain {
  has $.société;
  has $.salaire;
}

Le mot-clef is définit l’héritage.
Dans le Jargon orienté objet, on dit que Employé est un enfant (ou une classe fille) de Humain, et Humain est un parent d’Employé.

Toutes les classes filles héritent des attributs et des méthodes de la classe parente, il est donc inutile de les redéfinir.

9.7.2. Surcharge

Les classes héritent de tous les attributs et méthodes de leur classe parente.
Dans certains cas, nous avons besoin que la méthode d’un classe fille se comporte différemment de celle dont elle hérite.
Afin d’obtenir ce comportement, nous redéfinisson la méthode dans la classe fille.
Ce concept est nommé surcharge. Dans l’exemple ci-dessous, la classe Employé hérite de la méthode présentez-vous.

class Humain {
  has $.nom;
  has $.age;
  method présentez-vous {
    say "Bonjour je suis un être humain, et je m'appelle " ~ self.nom;
  }
}

class Employé is Humain {
  has $.société;
  has $.salaire;
}

my $françois = Humain.new(nom => 'François', age => 23,);
my $anne = Employé.new(nom => 'Anne', age => 25, société => 'Acme', salaire => 2000);

$françois.présentez-vous;
$anne.présentez-vous;

La surcharge se fait comme ceci:

class Humain {
  has $.nom;
  has $.age;
  method présentez-vous {
    say "Bonjour je suis un être humain, et je m'appelle " ~ self.nom;
  }
}

class Employé is Humain {
  has $.société;
  has $.salaire;
  method présentez-vous {
    say "Bonjour je suis un être humain, et je m'appelle " ~ self.nom ~ ' et je travaille chez: ' ~ self.société;
  }

}

my $françois = Humain.new(nom => 'François', age => 23,);
my $anne = Employé.new(nom => 'Anne', age => 25, société => 'Acme', salaire => 2000);

$françois.présentez-vous;
$anne.présentez-vous;

Suivant la classe dans laquelle se trouve l’objet, la bonne méthode sera appelée.

9.7.3. Sous-méthodes

Les Sous-méthodes sont un type de méthode dont les classes filles n’héritent pas.
Elles ne sont accessibles que depuis la classe dans laquelle elles sont déclarées.
Elles sont déclarées en utilisant le mot-clef submethod.

9.8. Héritage multiple

L’héritage multiple est disponible en Raku. Une classe peut hériter de plusieurs autres classes.

class barre-graph {
  has Int @.barre-valeurs;
  method dessiner {
    say @.barre-valeurs;
  }
}

class ligne-graph {
  has Int @.ligne-valeurs;
  method dessiner {
    say @.ligne-valeurs;
  }
}

class combo-graph is barre-graph is ligne-graph {
}

my $ventes-réelles = barre-graph.new(barre-valeurs => [10,9,11,8,7,10]);
my $ventes-prévisions = ligne-graph.new(ligne-valeurs => [9,8,10,7,6,9]);

my $réelles-vs-prévisions = combo-graph.new(barre-valeurs => [10,9,11,8,7,10],
                                            ligne-valeurs => [9,8,10,7,6,9]);
say "Ventes Réelles:";
$ventes-réelles.dessiner;
say "Ventes Prévisionelles:";
$ventes-prévisions.dessiner;
say "Réelles vs Prévisionelles:";
$réelles-vs-prévisions.dessiner;
Sortie
Ventes Réelles:
[10 9 11 8 7 10]
Ventes Prévisionelles:
[9 8 10 7 6 9]
Réelles vs Prévisionelles:
[10 9 11 8 7 10]
Explication

La classe combo-graph doit être capable de contenir deux séries, une pour les valeurs réelles dessinées sous forme de barres et une autre pour les valeurs prévisionelles dessinées sous forme de ligne.
C’est pourquoi nous l’avons définie comme fille de ligne-graph et barre-graph
Vous avez remarqué que l’appel de la méthode dessiner sur combo-graph n’a pas rendu les bons résultats.

Une seule série a été dessinée.
Que c’est-il passé?
combo-graph hérite de barre-graph et de ligne-graph; et chaque parent possède un méthode appelée dessiner. Quand nous appelons cette méthode sur combo-graph Raku résoudra le conflit en appelant une des méthodes héritées.

Correction

Pour avoir un comportement valide, nous devons surcharger la méthode dessiner dans combo-graph.

class barre-graph {
  has Int @.barre-valeurs;
  method dessiner {
    say @.barre-valeurs;
  }
}

class ligne-graph {
  has Int @.ligne-valeurs;
  method dessiner {
    say @.ligne-valeurs;
  }
}

class combo-graph is barre-graph is ligne-graph {
  method dessiner {
    say @.barre-valeurs;
    say @.ligne-valeurs;
  }
}

my $ventes-réelles = barre-graph.new(barre-valeurs => [10,9,11,8,7,10]);
my $ventes-prévisions = ligne-graph.new(ligne-valeurs => [9,8,10,7,6,9]);

my $réelles-vs-prévisions = combo-graph.new(barre-valeurs => [10,9,11,8,7,10],
                                            ligne-valeurs => [9,8,10,7,6,9]);
say "Ventes Réelles:";
$ventes-réelles.dessiner;
say "Ventes Prévisionelles:";
$ventes-prévisions.dessiner;
say "Réelles vs Prévisionelles:";
$réelles-vs-prévisions.dessiner;
Sortie
Ventes Réelles:
[10 9 11 8 7 10]
Ventes Prévisionelles:
[9 8 10 7 6 9]
Réelles vs Prévisionelles:
[10 9 11 8 7 10]
[9 8 10 7 6 9]

9.9. Rôles

Les rôles sont assimilables à des classes, dans le sens qu’ils sont une collection d’attributs et de méthodes.

Les rôles sont déclarés avec le mot-clef role, les classes qui veulent implémenter un rôle peuvent le faire en utilisant le mot-clef does.

Réécrivons l’exemple d’héritage multiple en utilisant des rôles:
role barre-graph {
  has Int @.barre-valeurs;
  method dessiner {
    say @.barre-valeurs;
  }
}

role ligne-graph {
  has Int @.ligne-valeurs;
  method dessiner {
    say @.ligne-valeurs;
  }
}

class combo-graph does barre-graph does ligne-graph {
  method dessiner {
    say @.barre-valeurs;
    say @.ligne-valeurs;
  }
}

my $ventes-réelles = barre-graph.new(barre-valeurs => [10,9,11,8,7,10]);
my $ventes-prévisions = ligne-graph.new(ligne-valeurs => [9,8,10,7,6,9]);

my $réelles-vs-prévisions = combo-graph.new(barre-valeurs => [10,9,11,8,7,10],
                                            ligne-valeurs => [9,8,10,7,6,9]);
say "Ventes Réelles:";
$ventes-réelles.dessiner;
say "Ventes Prévisionelles:";
$ventes-prévisions.dessiner;
say "Réelles vs Prévisionelles:";
$réelles-vs-prévisions.dessiner;

Si vous lancez le sript ci-dessus, vous constatez que les résultats sont les mêmes.

Vous vous demandez maintenant: si les rôles se comportent comme des classes, quelle est leur utilité?
Pour répondre à votre question, modifiez le premier script utilisé pour illustrer l’héritage multiple, celui où nous avons oublié de surclasser la méthode dessiner.

role barre-graph {
  has Int @.barre-valeurs;
  method dessiner {
    say @.barre-valeurs;
  }
}

role ligne-graph {
  has Int @.ligne-valeurs;
  method dessiner {
    say @.ligne-valeurs;
  }
}

class combo-graph does barre-graph does ligne-graph {
}

my $ventes-réelles = barre-graph.new(barre-valeurs => [10,9,11,8,7,10]);
my $ventes-prévisions = ligne-graph.new(ligne-valeurs => [9,8,10,7,6,9]);

my $réelles-vs-prévisions = combo-graph.new(barre-valeurs => [10,9,11,8,7,10],
                                            ligne-valeurs => [9,8,10,7,6,9]);
say "Ventes Réelles:";
$ventes-réelles.dessiner;
say "Ventes Prévisionelles:";
$ventes-prévisions.dessiner;
say "Réelles vs Prévisionelles:";
$réelles-vs-prévisions.dessiner;
Sortie
===SORRY!===
Method 'dessiner' must be resolved by class combo-graph because it exists in multiple roles (ligne-graph, barre-graph)
Explication

Si plusieurs rôles sont appliqués à la même classe, et un conflit survient, une erreur de compilation sera lancée.
Ceci est une approche plus sûre que l’héritage multiple, où les conflits ne sont pas pris comme des erreurs et sont résolus à l’exécution.

Les rôles vous avertirons en cas de conflit.

9.10. Introspection

L'introspection sert à obtenir des informations sur les propriétés d’un objet: comme son type, ses attributs ou ses méthodes.

class Humain {
  has $.nom;
  has $.age;
  method présentez-vous {
    say "Bonjour je suis un être humain, et je m'appelle " ~ self.nom;
  }
}

class Employé is Humain {
  has $.société;
  has $.salaire;
}

my $françois = Humain.new(nom => 'François', age => 23,);
my $anne = Employé.new(nom => 'Anne', age => 25, société => 'Acme', salaire => 2000);

$françois.présentez-vous;
$anne.présentez-vous;

say $françois.WHAT;
say $anne.WHAT;
say $françois.^attributes;
say $anne.^attributes;
say $françois.^methods;
say $anne.^methods;
say $anne.^parents;
if $anne ~~ Humain {say 'anne est un être humain'};

L’introspection est facilitée par:

  • .WHAT renvoie la classe depuis laquelle l’objet a été créé.

  • .^attributes renvoie une liste qui contient tous les attributs de l’objet.

  • .^methods renvoie toutes les méthodes implémentables de l’objet.

  • .^parents renvoie toutes les classes parentes de la classe à laquelle appartient l’objet.

  • ~~ est appelé l’opérateur de correspondance intelligente (smart-match). Il renvoie Vrai si l’objet correspond à sa classe de création ou à une de celles dont elle a hérité.

10. Gestion des exceptions

10.1. Capture des Exceptions

Les exceptions son un comportement particulier qui intervient quand quelque chose se passe mal à l’execution.
On dit que les exceptions sont lancées ou générées.

Le script ci-dessous, qui s’exécute correctement:

my Str $nom;
$nom = "Josiane";
say "Bonjour " ~ $nom;
say "Comment ça va?"
Sortie
Bonjour Josiane
Comment ça va?

Maintenant, voici un script analogue qui lance une exception:

my Str $nom;
$nom = 123;
say "Bonjour " ~ $nom;
say "Comment ça va?"
Sortie
Type check failed in assignment to $nom; expected Str but got Int (123)

Vous remarquerez que quand une erreur survient (ici affecter un nombre à une variable de chaîne), le programme s’arrête et les lignes suivantes ne seront pas évaluées, même si elles sont correctes.

La gestion des exceptions capture une exception qui a été lancée afin que le script puisse continuer à fonctionner.

my Str $nom;
try {
  $nom = 123;
  say "Hello " ~ $nom;
  CATCH {
    default {
      say "Pouvez vous nous redonner votre nom, nous ne l'avons pas trouvé dans le registre.";
    }
  }
}
say "Comment ça va?";
Sortie
Pouvez vous nous redonner votre nom, nous ne l'avons pas trouvé dans le registre.
Comment ça va?

La gestion d’exception se fait en utilisant un bloc try-CATCH.

try {
  #le code va ici
  #si quelque chose se passe mal le script entre dans le bloc CATCH
  #si tout se passe bien, le bloc CATCH sera ignoré
  CATCH {
    default {
      #le code présent ici ne sera évalué que si une exception a été lancée
    }
  }
}

Un bloc CATCH peut se définir de la même façon qu’un bloc given. Ce qui implique qu’on puisse faire un catch sur différent types d’exceptions.

try {
  #le code va ici
  #si quelque chose se passe mal le script entre dans le bloc CATCH
  #si tout se passe bien, le bloc CATCH sera ignoré
  CATCH {
    when X::AdHoc { #faire quelque chose si une exception de type X::AdHoc est lancée }
    when X::IO { #faire quelque chose si une exception de type X::IO est lancée }
    when X::OS { #faire quelque chose si une exception de type X::OS est lancée }
    default { #faire quelque chose si une exception est lancée qui ne correspond pas aux types précédents }
  }
}

10.2. Lancer des exceptions

A l’inverse de capturer les exceptions, Raku vous permet aussi d’en lancer.
On peut lancer deux types d’exceptions:

  • les exceptions ad-hoc

  • les exceptions typées

ad-hoc
my Int $age = 21;
die "Erreur !";
typed
my Int $age = 21;
X::AdHoc.new(payload => 'Erreur !').throw;

Les exceptions ad hoc sont lancées en utilisant la routine die suivie du message de l’exception.

Les exceptions typées sont des objets, d’où l’utilisation du constructeur .new() dans l’exemple ci-dessus.
Toutes les exceptions déscendent de la classe X, quelques exemples ci-dessous:
X::AdHoc est le type le plus simple d’exception
X::IO est lié aux erreurs IO (entrées/sorties)
X::OS est lié aux erreurs OS (système)
X::Str::Numeric est lié aux erreurs de conversion d’une chaîne vers un nombre.

Note
Pour une liste complète des types d’exceptions et une liste de leurs méthodes associées, allez sur https://docs.raku.org/type.html et naviguez dans les types qui commencent par X.

11. Expressions régulières

Une expression régulières, ou regex est une séquence de caractères utilisée pour le filtrage par motif.
La méthode la plus simple pour concevoir ce système, est d’y penser comme un motif qui peut être répété/isolé/extrait.

if 'ensoleillement' ~~ m/ soleil / {
    say "ensoleillement contient le mot soleil";
}

Dans cet exemple l’opérateur de correspondance intelligent (smart-match) ~~ est utilisé pour vérifier si une chaîne de caractères (ensoleillement) contient le mot (soleil).
"ensoleillement" est comparé à la regex m/ soleil /.

11.1. Définition d’une regex

Une expression régulière peut être définie comme suit:

  • /soleil/

  • m/soleil/

  • rx/soleil/

Les espaces, sauf si spécifiés comme requis explicitement, ne sont pas pris en compte: m/soleil/ and m/ soleil / sont identiques.

11.2. Correspondance par caractères

Les caractères alphanumériques et le tiret bas _ sont utilisés tels quels.
Tous les autres caractères doivent être "protégés" en les préfixant de la barre oblique inversée, ou mis entre guillemets (simples ou doubles).

Barre oblique inversée
if 'Température: 13' ~~ m/ \: / {
    say "La chaîne fournie contient le caractère deux-points :";
}
Guillemets droits simples
if 'Age = 13' ~~ m/ '=' / {
    say "La chaîne fournie contient le caractère égal = ";
}
Guillemets droits doules
if 'nom@société.com' ~~ m/ "@" / {
    say "Cette adresse mail semble valide car elle contient un caractère @";
}

11.3. Comparaison par catégories de caractères

Les caractères peuvent être réunis en catégories de reconnaissances.
On peut également utiliser la négation d’une catégorie (tout sauf cette catégorie):

Catégorie

Regex

Inverse

Regex

Caractère de mot (lettre, chiffre, ou tiret bas)

\w

Tout caractère sauf caractère de mot

\W

Chiffre

\d

Tout caractère sauf un chiffre

\D

Espace

\s

Tout caractère sauf espace

\S

Espace horizontal

\h

Tout caractère sauf espace Horizontal

\H

Espace vertical

\v

Tout caractère sauf espace vertical

\V

Tabulation

\t

Tout caractère sauf tabulation

\T

Saut de ligne

\n

Tout caractère sauf saut de ligne

\N

if "Robert123" ~~ / \d / {
  say "Pas de comparaison valide car pas de chiffres";
} else {
  say "Comparaison valide, car un chiffre"
}
if "John-Doe" ~~ / \s / {
  say "Cette chaîne contient un espace";
} else {
  say "Cette chaîne ne contient pas d'espace"
}

11.4. Propriétés unicode

La section précédente montre l’utilité d’utiliser des catégories de caractères.
Ceci dit, une approche plus systématique pourrait être d’utiliser les propriétés Unicode.
Les propriétés Unicode sont notées à l’intérieur de <: >

if "Robert123" ~~ / <:N> / {
  say "Contient un chiffre";
} else {
  say "Ne contient pas un chiffre"
}
if "Albert-Ebasque" ~~ / <:Lu> / {
  say "Contient une lettre en majuscule";
} else {
  say "Ne contient de lettre en majuscule"
}
if "Albert-Ebasque" ~~ / <:Pd> / {
  say "Contient un tiret";
} else {
  say "Ne contient pas un tiret"
}

11.5. Métacaractères

Les métacaractères (wildcards) peuvent également être utilisés dans les regex.

Le point . signifie n’importe quel caractère unique.

if 'abc' ~~ m/ a.c / {
    say "Correspondance";
}
if 'a2c' ~~ m/ a.c / {
    say "Correspondance";
}
if 'ac' ~~ m/ a.c / {
    say "Correspondance";
  } else {
    say "Pas de Correspondance";
}

11.6. Quantificateurs

Les quantificateurs viennent après un caractère et sont utilisés pour déterminer le nombre de fois qu’il doit apparaître.

Le point d’interrogation ? signifie zéro ou une fois.

if 'ac' ~~ m/ a?c / {
    say "Correspondance";
  } else {
    say "Pas de Correspondance";
}
if 'c' ~~ m/ a?c / {
    say "Correspondance";
  } else {
    say "Pas de Correspondance";
}

L’astérisque * signifie zéro ou plusieurs fois.

if 'az' ~~ m/ a*z / {
    say "Correspondance";
  } else {
    say "Pas de Correspondance";
}
if 'aaz' ~~ m/ a*z / {
    say "Correspondance";
  } else {
    say "Pas de Correspondance";
}
if 'aaaaaaaaaaz' ~~ m/ a*z / {
    say "Correspondance";
  } else {
    say "Pas de Correspondance";
}
if 'z' ~~ m/ a*z / {
    say "Correspondance";
  } else {
    say "Pas de Correspondance";
}

Le + signifie au moins une fois.

if 'az' ~~ m/ a+z / {
    say "Correspondance";
  } else {
    say "Pas de Correspondance";
}
if 'aaz' ~~ m/ a+z / {
    say "Correspondance";
  } else {
    say "Pas de Correspondance";
}
if 'aaaaaaaaaaz' ~~ m/ a+z / {
    say "Correspondance";
  } else {
    say "Pas de Correspondance";
}
if 'z' ~~ m/ a+z / {
    say "Correspondance";
  } else {
    say "Pas de Correspondance";
}

11.7. Résultats de correspondance

Quand la mise en correspondance avec une regex est positive, le résultat est stocké dans la variable spéciale $/

Script
if 'Rakudo est un compilateur Raku' ~~ m/compilateur/ {
    say "La correspondance est: " ~ $/;
    say "La chaîne avant la correspondance est: " ~ $/.prematch;
    say "La chaîne après la correspondance est: " ~ $/.postmatch;
    say "La chaîne reconnue commence à la position: " ~ $/.from;
    say "La chaîne reconnue finit à la position: " ~ $/.to;
}
Sortie
La correspondance est: compilateur
La chaîne avant la correspondance est: Rakudo est un
La chaîne après la correspondance est:  Raku
La chaîne reconnue commence à la position: 14
La chaîne reconnue finit à la position: 25
Explication

$/ renvoie un Objet de Correspondance (Match Object), c’est-à-dire la chaîne qui correspond à la regex
Les méthodes suivantes peuvent être appelées sur l'Objet de Correspondance:
.prematch renvoie la chaîne qui précède la correspondance.
.postmatch renvoie la chaîne qui suit la correspondance.
.from renvoie la position de départ de la correspondance.
.to renvoie la position de fin de la correspondance.

Tip
Par défaut le caractère espace n’est pas pris en compte.
Si nous voulons faire une correspondance avec une regex contenant un espace nous devons le définir explicitement.
Le :s dans la regex m/:s Raku 楽/ force l’espace à être considéré et non ignoré.
D’une autre façon, nous aurions pu écrire la regex comme ceci: m/ Perl\s楽/ en utilisant comme vu précédemment \s comme indicateur d’un espace.
Si une regex contient plusieurs espaces, utiliser :s devient plus efficace que l’utilisation de \s pour chaque espace.

11.8. Exemple

Vérifions si une adresse mail est valide ou non.
Pour la pertinence de cet exemple nous conviendrons qu’une adresse mail est valide si elle est formée comme suit:
prénom [point] nom [arobase] société [point] (com/org/net)

Warning
La regex utilisée ici pour la validation de mail est très insuffisante.
Elle ne sert ici qu’à titre d’exemple pour demontrer les fonctionnalités de regex de Raku.
Ne pas l’utiliser telle quelle en production.
Script
my $email = '[email protected]';
my $regex = / <:L>+\.<:L>+\@<:L+:N>+\.<:L>+ /;

if $email ~~ $regex {
  say $/ ~ " est une adresse mail valide";
} else {
  say "n'est pas une adresse mail valide";
}
Sortie

[email protected] est une adresse mail valide

Explication

<:L> correspond à une lettre
<:L>` correspond à une lettre ou plus + `\.` correspond à un caractère [point] + `\@` correspond a un caractère [arobase] + `<:L:N> correspond à une lettre ou un chiffre
<:L+:N>+ correspond à une lettre ou un chiffre, plusieurs fois

La regex peut être décomposée comme suit:

  • nom <:L>+

  • [point] \.

  • prénom <:L>+

  • [arobase] \@

  • nom de la société <:L+:N>+

  • [point] \.

  • com/org/net <:L>+

D’une autre façon, une regex peut être divisée en plusieurs regex nommées:
my $email = '[email protected]';
my regex lettres { <:L>+ };
my regex point { \. };
my regex arobase { \@ };
my regex lettres-et-chiffres { <:L+:N>+ };

if $email ~~ / <lettres> <point> <lettres> <arobase> <lettres-et-chiffres> <point> <lettres> / {
  say $/ ~ " est une adresse mail valide";
} else {
  say "Ce n'est pas une adresse mail valide";
}

Une regex nommée est définie en utilisant la syntaxe suivante: my regex nom-de-la-regex { définition de la regex }
Une regex nommée peut être appelée en utilisant la syntaxe suivante: <nom-de-la-regex>

Note
Pour plus de détails sur les regexes, voir https://docs.raku.org/language/regexes

12. Modules Raku

Raku est un langage à but générique. Il peut être utilisé pour remplir plusieurs taches, incluant: traitement d’un texte, image, web, bases de données, protocoles réseau, etc.

La réusabilité est un concept très important qui permet aux programmeurs de ne pas réinventer la roue à chaque nouvelle tache.

Raku permet la création et la redistribution de modules. Chaque module donne une série de fonctionnalités empaquetées, qui une fois installées, peuvent être réutilisées.

Zef et un outil de gestion de modules livré avec Rakudo.

Pour installer un module spécifique, taper la commande suivante dans votre terminal:

zef install "nom du module"

Note
Une liste des modules Raku se trouve ici: https://modules.raku.org

12.1. Utiliser les modules

MD5 est une fonction de hachage cryptographique qui produit une valeur de hachage de 128-bit.
MD5 a de nombreuses utilités, dont le chiffrage des mots de passe stockés dans une base de données. Quand un nouvel utilisateur s’inscrit, ses références ne sont pas stockées en texte brut, mais sous forme de hachage. L’idée derrière tout ceci est que si la base de données est compromise, l’attaquant ne pourra pas identifier les mots de passe.

Disons que vous ayez besoin d’un script qui génère un hachage MD5 sur un mot de passe avant stockage dans la base de données.

Heureusement, Raku possède un module qui implémente l’algorithme de hachage MD5. Installons-le:
zef install Digest::MD5

Maintenant, lançons le script suivant:

use Digest::MD5;
my $secret = "secret123";
my $secret-h = Digest::MD5.new.md5_hex($secret);

say $secret-h;

Pour lancer la fonction md5_hex() qui crée les hachages, nous devons charger le module voulu.
Le mot-clef use charge le module pour son utilisation dans le script.

Warning
Dans la pratique, le hachage MD5 n’est pas suffisant, car vulnérable aux attaques par dictionnaire.
Il doit être combiné avec un "salage" https://fr.wikipedia.org/wiki/Salage_(cryptographie).

13. Unicode

Unicode est un standard informatique de représentation du texte, qui prend en compte la la plupart des systèmes d’écriture dans le monde.
UTF-8 est un encodage de caractères, capable d’encoder tous les caractères (ou "Points de code") en Unicode.

Les caractères sont définis par un:
Graphème: Représentation visuelle.
Point de code: Un nombre assigné à un caractère.

13.1. Utiliser Unicode

Voyons comment nous pouvons générer des caractères en utilisant Unicode
say "a";
say "\x0061";
say "\c[LATIN SMALL LETTER A]";

Les trois lignes ci-dessus montrent les différentes façons de construire un caractère:

  1. Ecriture directe (graphème)

  2. Utilisation de \x suivit du point de code

  3. Utilisation de \c et du nom du point de code

Générons maintenant une émoticône (un smiley).
say "";
say "\x263a";
say "\c[WHITE SMILING FACE]";
Un autre exemple en combinant 2 points de code
say "á";
say "\x00e1";
say "\x0061\x0301";
say "\c[LATIN SMALL LETTER A WITH ACUTE]";

La lettre á peut être écrite:

  • en utilisant son point de code unique \x00e1

  • ou comme la combinaison des points de code de: a et de l’accent aigu \x0061\x0301

Quelques méthodes qui peuvent être utilisées:
say "á".NFC;
say "á".NFD;
say "á".uniname;
Output
NFC:0x<00e1>
NFD:0x<0061 0301>
LATIN SMALL LETTER A WITH ACUTE

NFC renvoie le point code unique.
NFD décompose le caractère et renvoie ses points de code.
uniname renvoie le nom du point de code.

Des caractères Unicode peuvent être utilisés en tant qu’identifants:
my $Δ = 1;
$Δ++;
say $Δ;

14. Parallélisme, Concurrence et Asynchronisme

14.1. Parallélisme

En temps normal, toutes les taches d’un programme s’effectuent de façon séquentielle. Ce n’est pas un grand problème si le temps d’exécution n’est pas crucial pour vous.

Raku intègre de façon naturelle le lancement de taches en parallèle.
À ce point, il est important de considérer que le parallélisme peut vouloir dire deux choses:

  • Parallélisme des taches: Deux (ou plus) expressions indépendantes lancées en parallèle.

  • Parallélisme des données: Un expression unique itérée sur une liste d’éléments en parallèle.

Commençons par ce dernier.

14.1.1. Parallélisme des données

my @tableau = (0..50000);                     #Remplissage du tableau
my @résultat = @tableau.map({ is-prime $_ }); #Appel de is-prime sur chaque élément
say now - INIT now;                           #Temps d'exécution du programme
Au regard de l’exemple ci-dessus:

Nous ne faisons qu’une seule opération @array.map({ is-prime $_ })
La sous-routine is-prime est appelée séquentiellement pour chaque élément du tableau:
is-prime @tableau[0] puis is-prime @tableau[1] puis is-prime @tableau[2] etc.

Nous pouvons heureusement appeler is-prime sur plusieurs éléments en même temps:
my @tableau = (0..50000);                          #Remplissage du tableau
my @résultat = @tableau.race.map({ is-prime $_ }); #Appel de is-prime sur chaque élément
say now - INIT now;                                #Temps d'exécution du programme

Notez l’emploi de race dans l’expression. Cette méthode permet l’itération en parallèle des éléments du tableau.

Après avoir lancé les deux exemples (avec et sans race), comparez les temps d’exécution de chaque programme.

Tip

race ne préservera pas l’ordre des éléments. Pour le préserver, utilisez à sa place hyper.

race
my @tableau = (1..1000);
my @résultat = @tableau.race.map( {$_ + 1} );
.say for @résultat;
hyper
my @tableau = (1..1000);
my @résultat = @tableau.hyper.map( {$_ + 1} );
.say for @résultat;

En lançant les deux exemples, vous remarquerez que l’un est trié et l’autre non.

14.1.2. Parallélisme des tâches

my @tab1 = (0..49999);
my @tab2 = (2..50001);

my @résultat1 = @tab1.map( {is-prime($_ + 1)} );
my @résultat2 = @tab2.map( {is-prime($_ - 1)} );

say @résultat1 eqv @résultat2;

say now - INIT now;
Voyons l’exemple ci-dessus:
  1. Nous avons défini deux tableaux

  2. Appliqué une opération itérative différente à chaque tableau, puis stocké les résultats

  3. Ensuite vérifié l’égalité des deux tableaux

Le script attend que @tab1.map( {is-prime($_ + 1)} ) finisse puis évalue @tab2.map( {is-prime($_ - 1)} )

Les opérations effectuées sur chaque tableau ne dépendent pas l’une de l’autre.

Pourquoi ne pas les effectuer en paralléle?
my @tab1 = (0..49999);
my @tab2 = (2..50001);

my $promesse1 = start @tab1.map( {is-prime($_ + 1)} ).eager;
my $promesse2 = start @tab2.map( {is-prime($_ - 1)} ).eager;

my @résultat1 = await $promesse1;
my @résultat2 = await $promesse2;

say @résultat1 eqv @résultat2;

say now - INIT now;
Explication

La méthode start évalue le code et renvoie un objet de type promesse, ou plus brièvement une promesse (promise).
Si le code est évalué correctement, la promesse sera tenue (kept).
Si le code lance une exception, la promesse sera non tenue (broken).

La méthode await s’attend à une promesse.
Si elle est tenue elle obtiendra les valeurs renvoyées.
Si elle n’est pas tenue, une exception sera lancée.

Vérifier le temps pris par chacun des scripts.

Warning
Le parallélisme ajoute toujours un surcoût lié à la gestion des processus en parallèle. Si cette surcharge n’est pas compensée par un gain dans le temps de calcul, le script paraîtra plus lent.
C’est pourquoi utiliser race, hyper, start et await avec des scripts assez simples peut effectivement les ralentir.

14.2. Concurrence et Asynchronisme

Note
Pour plus de détails sur la concurrence et la programmation asynchrone, voir https://docs.raku.org/language/concurrency

15. Interface d’appel natif

Raku donne la possibilité d’utiliser de librairies C en utilisant l’Interface d’appel natif. NativeCall est un module standard inclus dans Raku qui facilite son interfacage avec le langage C.

15.1. Appeler une fonction

Considérez le code C ci-dessous qui définit la fonction hellofromc. Cette fonction affiche sur le terminal Bonjour depuis C. Elle de prend pas d’argument et ne retourne aucune valeur.

ncitest.c
#include <stdio.h>

void hellofromc () {
  printf("Bonjour depuis\n");
}

Suivant votre système d’exploitation, lancez les commandes suivantes pour compiler ce code C dans une librairie.

Sur Linux:
gcc -c -fpic ncitest.c
gcc -shared -o libncitest.so ncitest.o
Sur Windows:
gcc -c ncitest.c
gcc -shared -o ncitest.dll ncitest.o
Sur macOS:
gcc -dynamiclib -o libncitest.dylib ncitest.c

Dans le même dossier où vous avez compilé votre librairie C, créez un nouveau fichier Raku qui contient le code suivant, et lancez-le.

ncitest.raku
use NativeCall;

constant LIBPATH = "$*CWD/ncitest";
sub hellofromc() is native(LIBPATH) { * }

hellofromc();
Explication:

D’abord nous avons déclaré l’utilisation du module NativeCall.
Puis nous avons créé une constante LIBPATH qui contient le chemin vers la librairie C.
Notez que $*CWD renvoie le dossier actuel de travail.
Puis nous avons créé une nouvelle routine Raku nomée hellofromc() qui va agir comme envellope pour la fonction C du même nom qui se trouve dans LIBPATH.
Cela est possible en utilisant le trait is native.
Puis nous appelons notre routine Raku.

Ce qui revient simplement à déclarer une routine avec un trait is native puis le nom de la librairie C.

15.2. Renommer une fonction

Dans la partie ci-dessus nous avons vu comment appler une simple fonction C en l’encepsulant dans une routine Raku qui a le même nom par l’utilisation du trait is native.

Dans certains cas, nous pourions avoir besoin de changer le nom de la routine Raku.
Pour ce-faire, nous utilisons le trait is symbol.

Modifions notre scrit Raku et renommons la routine hellofromc en hello.

ncitest.raku
use NativeCall;

constant LIBPATH = "$*CWD/ncitest";
sub hello() is native(LIBPATH) is symbol('hellofromc') { * }

hello();
Explication:

Si la routine Raku a un nom différent de son équivalente en C, nous devons utiliser le trait is symbol avec le nom de la fonction C originale.

15.3. Passer des arguments

Compilez la fonction C mofifiée et re-lancez le script ci-dessous.
Notez comment nous avons modifié le code C et Raku pour prendre en compte une chaîne (char* en C et Str avec Raku)

ncitest.c
#include <stdio.h>

void hellofromc (char* name) {
  printf("Bonjour, %s! Ceci est du C!\n", name);
}
ncitest.raku
use NativeCall;

constant LIBPATH = "$*CWD/ncitest";
sub hello(Str) is native(LIBPATH) is symbol('hellofromc') { * }

hello('Anne');

15.4. Retourner des valeurs

Reprenons le procédé encore une fois et créons une simple calculatrice qui additionne deux entiers.
Compilez la librairie C et lancez le script Raku.

ncitest.c
int add (int a, int b) {
  return (a + b);
}
ncitest.raku
use NativeCall;

constant LIBPATH = "$*CWD/ncitest";
sub add(int32,int32) returns int32 is native(LIBPATH) { * }

say add(2,3);

Notez que la fonction Raku comme la C accepte deux entiers et en renvoient un (int en C et int32 pour Raku)

15.5. Types

Vous avez pu vous interroger sur le fait que nous utilisons int32 au lieu de Int dans le script Raku.
Certains Types comme Int, Rat etc. ne peuvnet pas être utilisés pour recevoir des valeurs depuis un fonction C.
On doit utiliser les même Types en Raku que ceux utilisés en C.

Heureusement Raku possède de nombreus types qui ont leurs equivalents en C.

Typage C Typage Raku

char

int8

int8_t

short

int16

int16_t

int

int32

int32_t

int64_t

int64

unsigned char

uint8

uint8_t

unsigned short

uint16

uint16_t

unsigned int

uint32

uint32_t

uint64_t

uint64

long

long

long long

longlong

float

num32

double

num64

size_t

size_t

bool

bool

char* (String)

Str

Tableaux: Par exemple int* (Tableau d’entiers) and double* (Tableau de nombres de type double)

CArray: Par exemple CArray[int32] et CArray[num64]

Note
Pour plus d’informations sur l’interface d’appel natif, voir: https://docs.raku.org/language/nativecall

16. La communauté