Dit document is bedoeld om je een kort overzicht te geven van de Raku programmeertaal. Het is zo opgezet dat je snel leert iets met Raku te kunnen doen.

Sommige onderdelen van dit document verwijzen naar (completere en preciezere) delen van de (Engelstalige) Raku documentatie. Als je meer informatie over een bepaald onderwerp nodig hebt, is dat de beste plaats om die te vinden.

Je zult voorbeelden vinden bij de meeste onderwerpen in dit document. Neem de tijd om ze allemaal uit te proberen om ze beter te begrijpen.

Licentie

De inhoud is gelicenseerd met de Creative Commons Attribution-ShareAlike 4.0 International License.

Om deze in te zien, ga naar

Bijdragen

Als je wilt bijdragen aan dit document, ga dan naar:

Feedback

Alle commentaar is welkom:

1. Inleiding

1.1. Wat is Raku

Raku is een hogere programmeertaal voor algemeen gebruik met graduele typering. Men kan in Raku kan met diverse paradigma’s programmeren. Ondersteund worden onder andere procedureel, object-georiënteerd en functioneel programmeren.

Raku motto:
  • TIMTOWTDI (uitgesproken "Timtoodi", oftewel "TimToady" in het Engels): There Is More Than One Way To Do It. (Er is meer dan één manier om het te doen)

1.2. Jargon

  • Raku: Is een taalspecificatie met een verzameling tests. Een implementatie van Raku die al deze tests succesvol kan uitvoeren, mag zich een implementatie "Raku" noemen.

  • Rakudo: Is een compiler voor Raku.

  • Zef: Is een installatie-programma voor Raku modules.

  • Rakubrew: Is een installatie-programma voor Raku en een versie-manager.

1.3. Installeren van Raku

Er zijn een aantal manieren om Raku te installeren.

1.3.1. Rakubrew

De aanbevolen manier om Raku te installeren is door middel van Rakubrew, een installatie-programma voor Rak en tevens versie-manager.

Als eerste moet men Rakubrew installeren door een van de volgende commando’s uit te voeren, afhankelijk van het operating system dat men gebruikt.

On Linux, in een terminalvenster:
curl https://rakubrew.org/install-on-perl.sh | sh
On MacOS, in een terminalvenster:
curl https://rakubrew.org/install-on-macos.sh | sh
On Windows, in een PowerShell venster:
. {iwr -useb https://rakubrew.org/install-on-powershell.ps1 } | iex

Het programma eindigt met het tonen van een commando dat Rakubrew beschikbaar maakt zodra een terminalvenster wordt geopend.
Kopieer dat commando, voer het uit en sluit dan dat terminalvenster. En open dan een nieuw terminal venster.

Installeer dan de meest recente versie van Raku met:

rakubrew download

Om er zeker van te zijn dat de installatie is gelukt, voer dan dit commando uit:

raku --version

1.3.2. Installeerbare Componenten

De Rakudo compiler maakt ook installeerbare componenten beschikbaar voor Linux, MacOS en Windows.
Deze kunnen worden opgehaald worden van https://rakudo.org/downloads

Windows

Haal het installeer programma op (bestand met de .msi extensie) en pak het uit. Het installeer programma voegt Raku en Zef to aan het PATH.

Linux en MacOS

Haal het archief-bestand op (met de .tar.gz extensie) en pak het uit. De programma’s bevinden zich in de bin en share/perl6/site/bin mappen van het archief. Voeg deze mappen toe aan je PATH.

1.3.3. Rakudo Star

Rakudo Star is een bundel waarin zich Rakudo, Zef, een collectie van Raku modules en documentatie bevinden. Het kan worden geïnstalleerd met een van de volgende manieren:

  • Docker: docker pull rakudo-star

  • Scoop op Windows: scoop install rakudo-star

  • Homebrew op MacOS: brew install rakudo-star

Voor andere mogelijkheden, ga naar https://raku.org/install

1.4. Uitvoeren van Raku code

Je kunt eenvoudig Raku code uitvoeren in de REPL (Read-Eval-Print-Loop, oftewel een lees, evalueer, print, lus). Open daarvoor een terminalvenster, type raku en druk op [Enter]. Er verschijnt dan een > prompt. Vervolgens kun je een regel code intypen en weer op [Enter] drukken. De REPL zal dan de uiteindelijke waarde van die code laten zien op het scherm. Je kunt dan weer een regel code intypen, of exit intypen en op [Enter] drukken om de REPL te verlaten.

Je kunt je code natuurlijk ook opslaan in een bestand, dat je daarna gaat uitvoeren. We raden aan om een Raku script de extensie .raku te geven, zodat het later te herkennen is als Raku bestand. Voer het bestand uit door raku bestandsnaam.raku in het terminalvenster in te typen en op [Enter] te drukken. Anders dan bij de REPL zal die niet automatisch het resultaat van elke regel laten zien: daarvoor moet je een opdracht als say in je programma plaatsen om iets te tonen.

De REPL wordt meestal gebruikt om een specifiek stukje code uit te proberen, meestal niet meer dan één enkele regel. Voor programma’s die uit meer dan één regel bestaan, wordt het aangeraden om die regels in een bestand op te slaan en dan dat bestand uit te voeren.

Je kunt ook een regel code non-interactief uitproberen op de commando-regel in een terminalvenster, door raku -e 'jouw regel code' in te typen en dan op [Enter] te drukken.

Een regel-editor helpt je om alles uit een REPL te krijgen. Het maakt het mogelijk om de cursor-toetsen te gebruiken om vorige/volgende regels te herhalen, en de invoer aan te passen, waarbij TAB gebruikt kan worden voor woorden te completeren.

Indien dit nog niet werkt, dan kun je dit command uitvoeren:

zef install Terminal::LineEditor

1.5. Bewerkingsprogramma’s (Editors)

Aangezien je het grootste deel van je tijd Raku programma’s in bestanden aan het opslaan bent, is het handig om een goede editor te hebben die Raku syntax herkent.

1.6. Hello World!

Laten we beginnen met het hello world ritueel.

say 'hello world';

hetgeen ook geschreven kan worden als:

'hello world'.say;

1.7. Syntax overview

Raku kent weinig beperkingen: over het algemeen kun je zoveel spaties (witruimte) gebruiken als je zelf wilt. In een aantal gevallen is de witruimte wel van belang.

Opdrachten bestaan over het algemeen uit een regel code die beëindigd wordt door een punt-komma:

say "Hallo" if True;
say "Wereld" if False;

Punt-komma’s zijn niet nodig na de laatste opdracht in een bestand of een blok programma-code, maar het is een goed gebruik om ze dan in dat geval toch toe te voegen.

Een Blok kan bestaan uit nul of meer opdrachten. Je maakt een blok door accolades om de opdrachten heen te plaatsen.

{
    say "Eerste opdracht in het blok.";
    say "Tweede opdracht in het blok.";
}

Expressies zijn een speciaal soort opdracht die resulteren in een waarde: 1+2 geeft 3 terug als waarde

Expressies bestaan uit Termen en Operatoren.

Termen zijn:

  • Variabelen: Een waarde die bekeken en veranderd kan worden.

  • Literals (Letterlijke waarden): een constante waarde zoals een getal of een aantal letters (string).

Operatoren worden onderverdeeld in deze typen:

Type

Uitleg

Voorbeeld

Prefix

Voor een term

++1

Infix

Tussen twee termen

1+2

Postfix

Volgt na een term

1++

Circumfix

Staat om een term heen

(1)

Postcircumfix

Achter een term, om een andere term heen

Array[1]

1.7.1. Naamgeving

Je moet termen een naam geven op het moment dat je ze definieert.

Regels:
  • Ze moeten beginnen met een alphabetisch karakter of een liggend streepje (underscore).

  • Ze mogen cijfers bevatten (behalve als eerste karakter).

  • Ze mogen een of meer koppeltekens - en/of enkele aanhalingstekens ' bevatten (mits omgeven door alphabetische karakters, dus niet als eerste of laatste karakter).

Geldig

Niet geldig

var1

1var

var-one

var-1

var’one

var'1

var1_

var1'

_var

-var

Naamgevingsconventies:
  • Kameelkast (Camel case): variableNo1

  • Kebabkast (Kebab case): variable-no1

  • Slangenkast (Snake case): variable_no1

Je mag je termen namen geven zoals je zelf wilt, maar het is een goede gewoonte om vast te houden aan een enkele naamgevingsconventie in een programma.

Het gebruik van betekenisvolle namen zal jouw leven als programmeur gemakkelijker maken (en van anderen die later aan jouw programma moeten werken).

  • var1 = var2 * var3 is syntactisch correct, maar de betekenis is niet duidelijk.

  • maandsalaris = dagloon * gewerkte-dagen geeft beter aan waar het hierover gaat.

1.7.2. Commentaar

Een commentaar is een stuk tekst dat bij uitvoering genegeerd wordt, maar van belang kan zijn voor de lezer van de programma-code.

Er zijn 3 manieren om commentaren in een programma te stoppen:

  • Enkele regel:

    # Dit is een regel met commentaar
  • Als onderdeel van een regel (embedded):

    say #`(Dit is een ingebed commentaar) "Hallo wereld."
  • Meer dan één regel

    =begin comment
    Dit is een commentaar over meer dan één regel
    Commentaar 1
    Commentaar 2
    =end comment

1.7.3. Aanhalingstekens (Quotes)

Een string wordt gedefinieerd door middel van enkele of dubbele aanhalingstekens.

Gebruik altijd dubbele aanhalingstekens:

  • als er een enkel aanhalingsteken in de string voorkomt.

  • als de string een variabele bevat die geïnterpoleerd moet worden.

say 'Hallo Wereld';   # Hallo Wereld
say "Hallo Wereld";   # Hallo Wereld
say "Doe 't niet";    # Doe 't niet
my $naam = 'Jan Jansen';
say 'Hallo $naam';   # Hallo $naam
say "Hallo $naam";   # Hallo Jan Jansen

2. Operatoren

Operatoren zijn de symbolen en woorden die waarden combineren in expressies, bijvoorbeeld + in 1 + 2

2.1. Algemene operatoren

Onderstaande tabel toont de meest voorkomende operatoren.

2.1.1. Rekenkundig

Operator Type Beschrijving Voorbeeld Resultaat

+

Infix

Optelling

1 + 2

3

-

Infix

Aftrekking

3 - 1

2

*

Infix

Vermenigvuldiging

3 * 2

6

**

Infix

Machtsverheffen

3 ** 2

9

/

Infix

Delen

3 / 2

1.5

div

Infix

Geheel getal deling (rond af)

3 div 2

1

%

Infix

Modulo

7 % 4

3

%%

Infix

Deelbaarheid

6 %% 4

False

6 %% 3

True

gcd

Infix

Grootse gemene deler

6 gcd 9

3

lcm

Infix

Kleinste gemene veelvoud

6 lcm 9

18

==

Infix

Numeriek gelijk

9 == 7

False

!=

Infix

Numeriek niet gelijk

9 != 7

True

<

Infix

Numeriek kleiner dan

9 < 7

False

>

Infix

Numeriek groter dan

9 > 7

True

<=

Infix

Numeriek kleiner dan of gelijk aan

7 <= 7

True

>=

Infix

Numeriek groter dan of gelijk aan

9 >= 7

True

<=>

Infix

Numeriek meer/minder/gelijk

1 <=> 1.0

Same

1 <=> 2

Less

3 <=> 2

More

2.1.2. Vergelijken

Operator Type Beschrijving Voorbeeld Resultaat

eq

Infix

String gelijk

"Jan" eq "Jan"

True

ne

Infix

String niet gelijk

"Jan" ne "Jolanda"

True

lt

Infix

String kleiner dan

"a" lt "b"

True

gt

Infix

String groter dan

"a" gt "b"

False

le

Infix

String kleiner dan of gelijk

"a" le "a"

True

ge

Infix

String groter dan of gelijk

"a" ge "b"

False

leg

Infix

String meer/minder/gelijk

"a" leg "a"

Same

"a" leg "b"

Less

"c" leg "b"

More

cmp

Infix

Slimme meer/minder/gelijk

"a" cmp "b"

Less

3.5 cmp 2.6

More

~~

Infix

Slim vergelijken

2 ~~ 2

True

2 ~~ Int

True

"Raku" ~~ "Raku"

True

"Raku" ~~ Str

True

"verlichting" ~~ /licht/

「licht」

2.1.3. Toewijzing en strings

Operator Type Beschrijving Voorbeeld Resultaat

=

Infix

Toewijzing

my $var = 7

Wijst de waarde 7 toe aan de variabele $var

~

Infix

Strings aaneenschakelen

9 ~ 7

97

"Hallo " ~ "daar"

Hallo daar

x

Infix

String herhalen

13 x 3

131313

"Hallo " x 3

Hallo Hallo Hallo

2.1.4. Verhogen en verlagen

Operator Type Beschrijving Voorbeeld Resultaat

++

Prefix

Verhoging

my $var = 2; ++$var;

Verhoog de variabele met 1 en geef de verhoogde waarde terug: 3

Postfix

Verhoging

my $var = 2; $var++;

Geef de waarde van de variabele terug (2) en verhoog de variabele dan met 1

--

Prefix

Verlaging

my $var = 2; --$var;

Verlaag de variabele met 1 en geef de verlaagde waarde terug: 1

Postfix

Verlaging

my $var = 2; $var--;

Geef de waarde van de variabele terug (2) en verlaag die dan met 1

2.1.5. Conversie

Operator Type Beschrijving Voorbeeld Resultaat

+

Prefix

Converteer naar de numerieke waarde

+"3"

3

+True

1

+False

0

-

Prefix

Converteer naar de negatieve numerieke waarde

-"3"

-3

-True

-1

-False

0

?

Prefix

Converteer naar de logische waarde

?0

False

?9.8

True

?"Hello"

True

?""

False

my $var; ?$var;

False

my $var = 7; ?$var;

True

!

Prefix

Converteer naar het tegenovergestelde van de logische waarde

!4

False

2.1.6. Reeksen en lijsten

Operator Type Beschrijving Voorbeeld Resultaat

..

Infix

Reeks constructeur

0..5

Maak een reeks van 0 t/m 5

..^

Infix

Reeks constructeur

0..^5

Maak een reeks van 0 t/m 4

^..

Infix

Reeks constructeur

0^..5

Maak een reeks van 1 t/m 5

^..^

Infix

Reeks constructeur

0^..^5

maak een reeks van 1 t/m 4

^

Prefix

Reeks constructeur

^5

Zelfde als 0..^5, maakt een reeks van 0 t/m 4

…​

Infix

Luie reeks constructeur

0…​9999

Maak waarden alleen aan als daar expliciet om gevraagd wordt

|

Prefix

Pletten

|(0..5)

(0 1 2 3 4 5)

|(0^..^5)

(1 2 3 4)

2.2. Logische operatoren

Logische operatoren combineren condities of kunnen kiezen tussen 2 waarden aan de hand van een conditie.

Operator Type Beschrijving Voorbeeld Resultaat

and

Infix

True indien beide waarden waar zijn

True and True

True

True and False

False

or

Infix

True indien minstens één van de waarden waar is

True or False

True

False or False

False

&&

Infix

Zelfde als and met hogere prioriteit

True && False

False

||

Infix

Zelfde als or met hogere prioriteit

True || False

True

//

Infix

Gedefinieerd-of: linker waarde indien gedefinieerd, anders de rechter waarde

my $var; $var // 5;

5

my $var = 3; $var // 5;

3

?? !!

Ternary

Selecteer eerste waard indien conditie waar, anders de tweede waarde

7 > 5 ?? "yes" !! "no"

yes

7 < 5 ?? "yes" !! "no"

no

Logische operatoren worden meestal gebruikt om meerdere vergelijkingen te combineren.

my $age = 30;
say $age >= 18 && $age <= 65;   # True
say $age < 18 || $age > 65;     # False

De gedefinieerd-of operator // is een gemakkelijke manier of verstek waarde aan te geven.

my $name;
say $name // 'Anoniem';       # Anoniem

2.3. Omkeringsoperatoren

Door een R te plaatsen direct voor een operator, zorg je ervoor dat de termen omgewisseld worden.

Normale operator Resultaat Omkeringsoperator Resultaat

2 / 3

0.666667

2 R/ 3

1.5

2 - 1

1

2 R- 1

-1

2.4. Herleidingsoperatoren

Herleidingsoperatoren werken op lijsten.
Je maakt een herleidingsoperator door vierkante haken om de operator te plaatsen []

Normal Operator Resultaat Herleidingsoperator Resultaat

1 + 2 + 3 + 4 + 5

15

[+] 1,2,3,4,5

15

1 * 2 * 3 * 4 * 5

120

[*] 1,2,3,4,5

120

Voor een compleet overzicht van operatoren, inclusief hun prioriteit, ga dan naar https://docs.raku.org/language/operators

3. Variabelen

Raku variabelen kunnen worden geclassificeerd in 3 categorieën: Scalars, Arrays en Hashes.

Een sigil is een karakter dat als prefix gebruikt wordt om aan te geven in welke categorie een variabele hoort.

  • $ geeft een scalar aan

  • @ geeft een array aan

  • % geeft een hash aan

Een variabele moet worden gedefinieerd voordat het gebruikt kan worden.
Dit kan men doen door het woord my vóór de naam van de variabele te plaatsen.

Deze inleiding geeft een versimpeld beeld van variabelen, geschikt om de basis elementen van Raku te leren. Om meer te begrijpen over variabele, zie https://docs.raku.org/language/containers

3.1. Scalars

Een scalar kan één waarde bevatten.

#String
my $naam = 'Jan Jansen';
say $naam;

#Integer
my $leeftijd = 99;
say $leeftijd;

Afhankelijk van het type waarde dat een scalar bevat, kun je daar bepaalde operaties op uitvoeren.

3.1.1. String

my $naam = 'Jan Jansen';
say $naam.uc;
say $naam.chars;
say $naam.flip;
Uitvoer
JAN JANSEN
10
nesnaJ naJ
Bekijk https://docs.raku.org/type/Str voor de complete lijst van methoden die men op een string kan uitvoeren.

3.1.2. Integer

my $leeftijd = 17;
say $leeftijd.is-prime;
Uitvoer
True
Bekijk https://docs.raku.org/type/Int voor de complete lijst van methoden die men op een geheel getal (integer) kan uitvoeren.

3.1.3. Rationeel getal

my $leeftijd = 2.3;
say $leeftijd.numerator;
say $leeftijd.denominator;
say $leeftijd.nude;
Uitvoer
23
10
(23 10)
Bekijk https://docs.raku.org/type/Rat voor de complete lijst van methoden die men op een rationeel getal kan uitvoeren.

3.2. Arrays

Arrays bestaan uit een lijst van scalar variabelen.

my @dieren = 'kameel', 'lama', 'uil';
say @dieren;

Vele operaties kunnen op arrays uitgevoerd worden, zoals getoond in onderstaand voorbeeld:

De tilde ~ wordt gebruikt om strings aan elkaar te plakken.
Script
my @dieren = 'kameel','vicuña','lama';
say "De dierentuin heeft " ~ @dieren.elems ~ " dieren";
say "De dieren zijn: " ~ @dieren;
say "Ik ga een uil adopteren voor de dierentuin";
@dieren.push("owl");
say "Nu heeft mijn dierentuin: " ~ @dieren;
say "Het eerste dier dat we adopteerden was de " ~ @dieren[0];
@dieren.pop;
say "Helaas is de uil ontsnapt, dus hebben we nu alleen nog: " ~ @dieren;
say "We gaan de dierention sluiten en houden nog maar één dier over";
say "We laten de " ~ @dieren.splice(1,2) ~ " gaan en houden de " ~ @dieren;
Uitvoer
De dierentuin heeft 3 dieren
De dieren zijn: kameel vicuña lama
Ik ga een uil adopteren voor de dierentuin
Nu heeft mijn dierentuin: kameel vicuña lama uil
Het eerste dier dat we adopteerden was de kameel
Helaas is de uil ontsnapt, dus hebben we nu alleen nog: kameel vicuña lama
We gaan de dierention sluiten en houden nog maar één dier over
We laten de vicuña llama gaan en houden de kameel
Uitleg

.elems geeft het aantal elementen in een array.
.push() voegt een element toe aan een array.
We kunnen een specifiek element van een array bekijken door de positie aan te geven @dieren[0].
.pop verwijdert het laatste element van het array.
.splice(a,b) verwijdert b elementen vanaf positie a.

Hieronder vind je nog een paar voorbeelden van operaties die op een array kunnen worden uitgevoerd:

Script
my @dieren = 'kameel', 'vicuña', 'lama';
say "Het laatste dier is een " ~ @dieren[*-1];
say "De eerste twee dieren zijn: " ~ @dieren[0, 1];
say "Allo dieren: " ~ @dieren.join(', ');
@dieren.unshift("uil");
say "Een uil kwam er als eerste bij: " ~ @dieren;
@dieren.shift;
say "De uil is alweer gevlogen: " ~ @dieren;
Uitvoer
Het laatste dier is een lama
De eerste twee dieren zijn: kameel vicuña
Alle dieren: kameel, vicuña, lama
Een uil kwam er als eerste bij: uil kameel vicuña lama
De uil is alweer gevlogen: kameel vicuña lama
Uitleg

@dieren[*-1] geeft het laatste element van een array.
@dieren[0, 1] geeft meerdere elementen in één keer. Dit heet een plak (slice).
.join(', ') geeft een enkele string gemaakt door alle elementen, gescheiden door een komma en een spatie, samen te voegen.
.unshift() voegt één of meer elementen toe aan het begin van een array.
.shift verwijdert het eerste element van een array en geeft dat terug.

3.2.1. Veranderingen in een array zelf

Sommige methoden veranderen de array waar ze op worden aangeroepen. Anderd laten de array onveranderd en geven een waarde terug.
Het is belangrijk om te weten welke method wat doet.

Script
my @nummers = 7, 2, 4, 9, 11, 3;

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

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

@nummers .= sort;
say @nummers;      #4
Uitvoer
[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
Uitleg

.push verandert een array. Een methode die dat doet, noemen we een mutator (#1)

.sort laat een array onveranderd. Geeft de waarden van de array gesorteerd terug:

  • (#2) laat de gesorteerde waarden zien die terug zijn gegeven.

  • (#3) laat zien dat de array niet is veranderd.

Om het resultaat van een zo’n methode direct in het array terug te plaatsen gebruiken we .= in plaats van . (#4)
@nummers .= sort is een kortere manier om @nummers = @nummers.sort te schrijven

Het resultaat in #2 werd getoond met (ronde) haken in plaats van vierkante haken omdat .sort een lijst teruggeft in plaats van een array. Nadat die waarden weer in @nummers is geplaatst, is het weer een array geworden.
.= werkt met alle methodes en op alle variabelen. Bijvoorbeeld, $naam .= uc vervangt de waarde van $naam met de versie in hoofdletters.

3.2.2. Arrays met beperkt aantal elementen

Een gewoon array kun je als volgt specificeren:

my @array;

Een gewoon array is niet beperkt wat betreft aantal elementen, het past zichzelf aan (auto-extending).
Men kan in een gewoon array zoveel waarden opslaan als men wil.

Daarentegen is het ook mogelijk om een array aan te maken met een beperkt aantal elementen. Dit soort arrays verbieden toegang tot niet-bestaande elementen.

Specificeer het aantal elementen in vierkante haken direct achter de naam van een array om een array met beperkt aantal elementen te specificeren:

my @array[3];

Dit array kan hoogstens 3 waarden bevatten, met als indexwaarden 0 t/m 2.

my @array[3];
@array[0] = "eerste waarde";
@array[1] = "tweede waarde";
@array[2] = "derde waarde";

Het is niet mogelijk om een vierde waarde aan dit array toe te voegen:

my @array[3];
@array[0] = "eerste waarde";
@array[1] = "tweede waarde";
@array[2] = "derde waarde";
@array[3] = "vierde waarde";
Index 3 for dimension 1 out of range (must be 0..2)

3.2.3. Multidimensionele arrays

De arrays die we tot nu toe gezien hebben, hadden maar één dimensie.
We kunnen echter ook arrays met meer dan één dimensie in Raku specificeren.

my @tbl[3;2];

Dit array heeft 2 dimensies. De eerste dimensie kan maximaal 3 waarden hebben, en de tweede dimensie maximaal 2 waarden.

Zie het als een rooster van 3x2.

my @tbl[3;2];
@tbl[0;0] = 1;
@tbl[0;1] = "x";
@tbl[1;0] = 2;
@tbl[1;1] = "y";
@tbl[2;0] = 3;
@tbl[2;1] = "z";
say @tbl
Uitvoer
[[1 x] [2 y] [3 z]]
Visuele weergave van het array:
[1 x]
[2 y]
[3 z]
Zie https://docs.raku.org/type/Array voor volledige informatie over arrays.

3.3. Hashes

3.3.1. Het maken van een hash

Een Hash is een verzameling van naam/waarde paren (key/value pairs)
my %hoofdsteden = 'VK','Londen','Duitsland','Berlijn';
say %hoofdsteden;
Uitvoer
{Duitsland => Berlijn, VK => Londen}
Een andere manier om een hash te vullen:
my %hoofdsteden = VK => 'Londen', Duitsland => 'Berlijn';
say %hoofdsteden;
Uitvoer
{Duitsland => Berlijn, VK => Londen}

Dit zijn een aantal van de methoden die men op een hash kan uitvoeren:

Script
my %hoofdsteden = VK => 'Londen', Duitsland => 'Berlijn';
%hoofdsteden.push: (Frankrijk => 'Parijs');
say %hoofdsteden.kv;
say %hoofdsteden.keys;
say %hoofdsteden.values;
say "De hoofdstad van Frankrijk is: " ~ %hoofdsteden<Frankrijk>;
Uitvoer
(Frankrijk Parijs Duitsland Berlijn VK Londen)
(Frankrijk Duitsland VK)
(Parijs Berlijn Londen)
De hoofdstad van Frankrijk is: Parijs
Uitleg

.push: (naam => 'Waarde') voegt een nieuwe naam/waarde paar toe.
.kv geeft een lijst met alle namen en waarden terug.
.keys geeft een lijst met alle namen terug.
.values geeft een lijst met alle waarden terug.
De waarde behorende bij een gegeven naam kun je opvragen door die naam te specificeren %hash<naam>

Raku houdt geen bepaalde volgorde van de namen in een hash aan. The volgorde waarin de landen in de bovenstaande uitvoer kan anders zijn, en ook verschillen met elke keer dat je die code uitvoert.

3.3.2. Controleren en verwijderen van een naam

Men kan ook controleren of een naam bestaat in een hash, en de naam eruit verwijderen.

Script
my %hoofdsteden = VK => 'Londen', Duitsland => 'Berlijn';
say %hoofdsteden<Duitsland>:exists;
say %hoofdsteden<Frankrijk>:exists;
%hoofdsteden<Duitsland>:delete;
say %hoofdsteden;
Uitvoer
True
False
{VK => Londen}
Uitleg

%hash<key>:exists geeft True indien de naam bestaat in de hash.
%hash<key>:delete verwijdert de naam uit de hash.

Zie https://docs.raku.org/type/Hash voor alle informatie over hashes.

3.4. Types

3.4.1. Dynamische en statische typeringen

In de voorafgaande voorbeelden hebben we niet het type van de waarde aangegeven die in een variabele opgeslagen kan worden.

.WHAT geeft het type van de waarde in een variabele terug.
my $var = 'Tekst';
say $var;
say $var.WHAT;

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

Zoals je kunt zien in bovenstaand voorbeeld, was het type van de waarde in $var eerst (Str) en daarna (Int).

Deze stijl van programmeren wordt dynamische typering (dynamic typing) genoemd. Dynamisch in de betekenis dat de variable waarden mag bevatten van elk (Any) type.

Probeer nu onderstaand voorbeeld uit te voeren:
Merk op dat we Int voor de naam van de variabele hebben geplaatst.

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

Dit zal fout gaan en terug komen met het foutbericht: Type check failed in assignment to $var; expected Int but got Str

Wat hier gebeurde is dat we van te voren hadden aangegeven dat de variabele alleen maar (Int) mag accepteren. Toen we probeerden om er een string (Str) aan toe te wijzen, was dat niet mogelijk en ging het fout.

Deze stijl van programmeren wordt "statische typering" (static typing) genoemd. Statisch omdat het type van variabelen wordt gedefinieerd voordat er aan wordt toegewezen, en deze later niet kan worden veranderd.

Raku wordt aangeduid met "graduele typering": het laat namelijk zowel statische als dynamische typering toe.

3.4.2. Getypeerde arrays en hashes

Arrays en hashes kunnen ook statisch getypeerd worden:
my Int @array = 1,2,3;
say @array;
say @array.WHAT;

my Str @veeltalig = "Hello","Salut","Hallo","您好","안녕하세요","こんにちは";
say @veeltalig;
say @veeltalig.WHAT;

my Str %hoofdsteden = (VK => 'Londen', Duitsland => 'Berlijn');
say %hoofdsteden;
say %hoofdsteden.WHAT;

my Int %landennummers = (VK => 44, Duitsland => 49);
say %landennummers;
say %landennummers.WHAT;

3.4.3. Veel voorkomende types

Hieronder vind je een lijst van meest voorkomende typen:
Je zult hoogstwaarschijnlijk de eerste twee nooit gebruiken, maar we laten ze hier zien om je te laten weten dat ze bestaan.

Type

Beschrijving

Voorbeeld

Resultaat

Mu

De ultieme basis van de Raku typen hierarchie

Any

Het basis type voor nieuwe klassen en de meeste standaard klassen

Cool

Waarden die zowel als string of als getal kunnen worden beschouwd

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

13 62

Str

Een string: reeks van karakters

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

NOEN

Int

Integer (elke gewenste precisie)

7 + 7

14

Rat

Rationeel nummer (beperkte precisie)

0.1 + 0.2

0.3

Bool

Boolean

!True

False

3.5. Introspectie

Met introspectie bedoelen we het process waarmee we informatie over de eigenschappen van een object kunnen bekijken, zoals het type.
In een van de vorige voorbeelden gebruikten we .WHAT om het type van een variabele te achterhalen.

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)

Het type van een variabele waarin een waarde is opgeslagen, is gecorreleerd aan die waarde.
Het type van een lege variabele die gespecificeerd is met een type, is het type waarmee het werd gespecificeerd.
Het type van een lege variabele die niet is gespecificeerd met een type, is (Any)
Om de waarde uit een variabele te verwijderen, kun je de waarde Nil toewijzen.

3.6. Bereik (scoping)

Voordat men een variabele voor de eerste keer kan gebruiken, moet deze worden gedefinieerd.

3.6.1. Statisch bereik

Dit kan op diverse manieren in Raku, my is wat we tot nu toe in de bovenstaande voorbeelden hebben gebruikt.

my $var = 1;

Met my geeft men de variabele een statisch bereik (ook wel lexicaal bereik genoemd). In andere woorden, de variabele zal alleen maar toegankelijk zijn in het gebied (scope) waarin het was gedefinieerd.

Zo’n gebied (scope) wordt in Raku begrensd door { }. Een variabele is toegankelijk in het gehele Raku script als er geen gebiedsbegrenzing is.

{
    my Str $var = 'Tekst';
    say $var; # is toegankelijk
}
say $var; #is niet toegankelijk, geeft een foutmelding

Aangezien zo’n variabele alleen toegankelijk is in het gebied waarin het was gedefinieerd, kan men dezelfde naam voor een variabele gebruiken in een ander gebied.

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

3.6.2. Andere bereiken

Andere manieren om een bereik / toestand van een variabele aan te geven.

  • our geeft een variabele aan in het bereik van een package. Het is zichtbaar buiten het bereik waarin het wordt gedefinieerd.

  • constant geeft een "variabele" aan die niet kan worden veranderd.

constant $tau = pi * 2;
$tau = 6;   # geeft foutmelding: Cannot assign to an immutable value

3.7. Toewijzing vs. verbinden

3.7.1. Toewijzing

We hebben in de vorige voorbeelden gezien hoe we waarden aan variabelen kunnen toewijzen.
Toewijzing wordt gedaan met de = operator.

my Int $var = 123;
say $var;

We kunnen de waarde van een variabele veranderen:

Script
my Int $var = 123;
say $var;
$var = 999;
say $var;
Uitvoer
123
999

3.7.2. Verbinden

Daarentegen kunnen we de waarde van een variabele niet veranderen als deze is verbonden met een waarde.
Verbinding wordt gedaan met de := operator.

Script
my Int $var := 123;
say $var;
$var = 999;
say $var;
Uitvoer
123
Cannot assign to an immutable value

3.7.3. Verbinden met variabelen

Variabelen kunnen ook verbonden worden met andere variabelen:

Script
my $a;
my $b;
$b := $a;
$a = 7;
say $b;
$b = 8;
say $a;
Uitvoer
7
8

Het verbinden van variabelen werkt twee kanten op, zoals je al gezien hebt.
$a := $b en $b := $a hebben hetzelfde effect.

Zie https://docs.raku.org/language/variables voor meer informatie over variabelen.

4. Strings

We hebben strings al vele malen gebruikt in vorige voorbeelden.
In dit hoofdstuk gaan we wat dieper in op wat er allemaal met strings kan worden gedaan.

4.1. Interpolatie

Een string gedefineerd met dubbele aanhalingstekens vervangt de variabelen met hun waarde.
Dit wordt "interpolatie" genoemd.

my $naam = 'Jan';
my $leeftijd = 23;
say "$naam is $leeftijd jaar oud";

Interpolatie is niet beperkt tot variabelen. Elk stuk programma code dat omgeven wordt door accolades zal worden uitgevoerd, en het resultaat daarvan wordt in de string geplaatst.

Script
my $naam = 'Jan';
my $leeftijd = 23;
say "$naam wordt volgend jaar {$leeftijd + 1}";
say "De naam $naam heeft {$naam.chars} letters";
say 'De naam $naam heeft {$naam.chars} letters";
Uitvoer
Jan wordt volgend jaar 24.
De naam Jan heeft 3 letters.
'De naam $naam heeft {$naam.chars} letters";

De laatste string werd gemaakt met enkele aanhalingstekens, waarin niets wordt geïnterpoleerd.

4.2. Word lists

A list of words can be written without quotes and without commas, by enclosing it in angle brackets.

my @dieren = <kameel lama uil>;
say @dieren;

Is een kortere manier van schrijven dan:

my @dieren = 'kameel', 'lama', 'uil';
say @animals;
De woorden worden gescheiden door spaties. Deze notatie kan niet worden gebruikt voor waarden waarin zich een spatie bevindt.

4.3. Veel gebruikte methoden

Op een string kunnen vele methoden worden aangeroepen, zoals hieronder is te zien:

Script
my $tekst = 'Raku is lol';
say $tekst.chars;
say $tekst.uc;
say $tekst.lc;
say $tekst.flip;
say $tekst.contains('lol');
say $tekst.starts-with('Raku');
say $tekst.ends-with('boring');
say $tekst.substr(0, 4);
say $tekst.index('is');
say '   Raku   '.trim;
Uitvoer
11
RAKU IS LOL
raku is lol
lol si ukaR
True
True
False
Raku
5
Raku
Uitleg

.chars geeft het aantal karakters in de string.
.uc en .lc geeft de string in hoofdletters of kleine letters.
.flip geeft de string met de karakters achterstevoren.
.contains() geeft aan of een string in de string voorkomt.
.starts-with() en .ends-with() geeft aan of een string begint of eindigt met een andere string.
.substr(a, b) geefts b karakters beginnend op positie a.
.index() geeft de positie van een string in een andere string.
.trim geeft een string zonder de witruimte aan begin en einde van de string

Geen van deze methoden verandert de originele string. Gebruik .= om het resultaat in de variabele terug te plaatsen, zoals we al eerder deden met arrays.

4.4. Splijten en samenvoegen

Een string kan worden gespleten in een lijst, en een lijst kan worden samengevoegd tot een string.

Script
my $zin = "Pa's wijze lynx bezag";
my @woorden = $zin.woorden;
say @woorden;
say @woorden.elems;
say @woorden.join('-');

my $record = 'Jan,23,Nederlander';
my @velden = $record.split(',');
say @velden;
say @velden[0];

say 'Raku'.comb;
Uitvoer
[Pa's wijze lynx bezag]
4
Pa's-wijze-lynx-bezag
[Jan 23 Nederlander]
Jan
(R a k u)
Uitleg

.words splijt een string in woorden.
.join() combineert de gegeven lijst in een string met de gegeven separator tussen de elementen.
.split() splijt een string op de gegeven separator.
.comb geeft een lijst met elke letter van de string apart..

4.5. Formatteren

sprintf bouwt een string naar aanleiding van het gegeven sjabloon.
Het sjabloon bevat formatteer aanduidingen die beginnen met %. Elke aanduiding wordt vervangen door een van de waarden die worden meegegeven.

Script
say sprintf('%s is %d jaar oud', 'Jan', 23);
say sprintf('De prijs is %.2f euro', 3.14159);
say sprintf('Kaart nummer %05d', 42);
Uitvoer
Jan is 23 jaar oud
De prijs is 3.14 euro
Kaart nummer  00042
Explanation

%s geeft een string aan.
%d geeft een geheel getal (integer) aan.
%.2f geeft een getal afgerond op 2 decimalen aan.
%05d geeft een geheel getal, uitgevuld tot 5 posities.

Voor de complete lijst van methoden die op strings kunnen worden uitgevoerd, zie https://docs.raku.org/type/Str

5. Lussen en condities

Raku heeft een veelheid aan conditionele- en lusconstructies.

5.1. if

De code in het bereik van de conditionele constructie wordt alleen maar uitgevoerd als de conditie waar (True) is.

my $leeftijd = 19;

if $leeftijd > 18 {
    say 'Welkom'
}

In Raku kunnen we de volgorde van de code en de conditie omkeren.
Maar zelfs als de volgorde is omgekeerd, zal de conditie altijd eerst worden uitgevoerd.

my $leeftijd = 19;

say 'Welkom' if $leeftijd > 18;

We kunnen alternatieve bereiken voor uitvoering aangeven voor het geval dat de conditie niet waar is:

  • else

  • elsif

#voer deze code uit voor verschillende waarden van de variabele
my $aantal-stoelen = 9;

if $aantal-stoelen <= 5 {
    say 'Ik ben een personenauto'
} elsif $aantal-stoelen <= 7 {
    say 'Ik ben een busje'
} else {
    say 'Ik ben een bus'
}

5.2. unless

De tegenovergestelde, ontkennende versie van een if command is unless (tenzij).

Deze code:

my $schone-schoenen = False;

if not $schone-schoenen {
    say 'Maak je schoenen schoon'
}

Kan geschreven worden als:

my $schone-schoenen = False;

unless $schone-schoenen {
    say 'Maak je schoenen schoon'
}

Ontkenning (negation) wordt in Raku gedaan met ! of not.

unless (conditie) kan worden gebruikt in plaats van if not (conditie).

unless kan geen else bereik hebben.

5.3. with

with gedraagt zich als een if commando, maar kijkt of de variabele een waarde heeft.

my Int $var=1;

with $var {
    say 'Hallo'
}

Als je deze code uitvoert zonder dat je een waarde aan de variabele hebt toegekend, dan zou je geen uitvoer moeten zien.

my Int $var;

with $var {
    say 'Hallo'
}

without is de ontkennende versie van with. Net als unless van if.

Als de eerste with niet waar is, dan kan men een alternatief bereik aangeven met orwith.
Je kunt with en orwith zien als een soort if en elsif.

5.4. for

Met het for commando kun je over een aantal waarden repeteren.

5.4.1. Arrays

my @array = 1,2,3;

for @array -> $array-item {
    say $array-item * 100
}

Merk op dat we een lusvariabele $array-item aanmaken om de operatie *100 op elk element van het array uit te kunnen voeren.

Indien er geen lusvariabele wordt gegeven, dan wordt elke waarde in de variabele $_ geplaatst, ook wel bekend als "de verstek-variabele".

my @array = 1, 2, 3;

for @array {
    say $_ * 100;
}

5.4.2. Reeksen

De waarden kunnen ook van een reeks afkomen:

for 1..5 -> $nummer {
    say $nummer;
}

Men kan ook een methode aanroepen op de verstek-variable door alles voor de punt weg te laten.
Met andere woorden, .say is een kortere schrijfwijze voor $_.say

for 1..5 {
    .say;
}

5.4.3. Hashes

Om alle waarden van een hash te bekijken, kan men met de kv methode vragen om de namen en de waarden in the hash, en daar twee lusvariabelen voor aanmaken:

my %hoofdsteden = VK => 'Londen', Duitsland => 'Berlijn';

for %hoofdsteden.kv -> $land, $stad {
    say "De hoofdstad van  $lang is $stad";
}

5.5. given

given is het Raku equivalent van het switch commando in andere programmeertalen, maar het is veel krachtiger.

my $var = 42;

given $var {
    when 0..50 { say 'Minder dan of gelijk aan 50'}
    when Int { say "is een Int" }
    when 42  { say 42 }
    default  { say "huh?" }
}

Het testen van condities stops zodra een conditie van een when waar is geweest.

Met proceed kun je in Raku aangeven dat je door wilt gaan met testen van condities nadat een conditie waar was.

my $var = 42;

given $var {
    when 0..50 { say 'Minder dan of gelijk aan 50';proceed}
    when Int { say "is een Int";proceed}
    when 42  { say 42 }
    default  { say "huh?" }
}

5.6. loop

loop is een andere manier om een for lus aan te geven.

In feite is loop precies zoals for lussen geschreven worden in de familie C-programmeertalen.

loop (my $i = 0; $i < 5; $i++) {
    say "Het huidige nummer is $i"
}

5.7. while en until

De while lus herhaalt executie van een blok zolang de conditie waar is.

my $countdown = 3;

while $countdown > 0 {
    say $countdown;
    $countdown--;
}
say 'Gestart!';
Uitvoer
3
2
1
Gestart!

until is tegenovergestelde versie van while. Het herhaalt het blok zolang de conditie niet waar is.
Dat is te vergelijken met unless.

Bovenstaand voorbeeld kan ook worden geschrene als

my $countdown = 3;

until $countdown == 0 {
    say $countdown;
    $countdown--;
}
say 'Gestart!';
Je moet er wel voor zorgen dat de conditie wel op een gegeven ogenblik verandert, anders zal deze lus nooit tot een einde komen.

5.8. next en last

next en last geven ons meer controle over een lus. Ze kunnen worden gebruikt in een for, loop, while en until lus.

  • next next negeert de rest van het blok en gaat verder met de volgende waarde

  • last springt uit de lus.

for 1..10 -> $nummer {
    next unless $nummer %% 2;
    last if $nummer > 8;
    say $nummer;
}
Uitvoer
2
4
6
8
Explanation

next unless $number %% 2 negeert de getallen die niet door 2 deelbaar zijn
last if $number > 8 stopt de lus zodra de waarde meer is dan 8, dus 10 zal nooit getoond worden.

Zie https://docs.raku.org/language/control voor meer informatie over conditionele- en lusconstructies.

6. I/O

De twee meest voorkomende manieren van Invoer/Uitvoer zijn Terminal en Bestanden.

6.1. Basic I/O op de Terminal

6.1.1. say

say schrijft naar de standaard uitvoer. Het voegt een regeleinde (newline) toe aan het einde. In andere woorden, de volgende code:

say 'Hallo mevrouw.';
say 'Hallo meneer.';

worden op 2 aparte lijnen getoond.

6.1.2. print

Aan de andere kant doet print precies hetzelfde, maar het voegt geen regeleinde toe.

Probeer eens om de say door een print te vervangen en vergelijk de resultaten.

6.1.3. get

Men kan get gebruiken om invoer van de terminal te krijgen.

my $naam;

say "Hoi, hoe heet je?";
$naam = get;

say "Welkom bij Raku, beste $naam";

Als je bovenstaande code uitvoert zal de terminal wachten tot je je naam intypt en op [Enter] drukt. Vervolgens zal het je begroeten.

6.1.4. prompt

prompt is een combinatie van print en get.

Het bovenstaande voorbeeld kan ook worden geschreven als:

my $naam = prompt "Hoi, hoe heet je? ";

say "Welkom bij Raku, beste $naam";

6.2. Uitvoeren van externe commando’s

Deze twee subroutines kunnen worden gebruikt om externe commando’s uit te voeren:

  • run voert een extern commando direct uit.

  • shell voert een extern commando uit alsof je het hebt ingetypt op een commando regel (via een z.g. "shell"). Het hangt af van de systeem software die je gebruikt. Alle meta-karakters worden geïnterpreteerd door de shell, inclusief z.g. "pipes", "redirects" en specificaties van environment variabelen.

Voer dit uit als je met Linux/OS X werkt
my $naam = 'Neo';
run 'echo', "hallo $naam";
shell "ls";
Voer dit uit als je met Windows werkt
shell "dir";

echo en ls zijn veel voorkomende commando’s op Linux/OS X:
echo drukt de argumenten af (het equivalent van say in Raku)
ls laat alle bestanden en directories zien in de huidige directory

dir is het equivalent van ls bij Windows.

6.3. File I/O

6.3.1. slurp

Men kan slurp gebruiken om een geheel bestand in te lezen.

Maak een tekstbestand aan met de volgende inhoud:

scores.txt
Jan 9
Japie 7
Jolanda 8
Jessica 7
my $data = slurp "scores.txt";
say $data;

6.3.2. spurt

Men kan spurt gebruiken om data naar een bestand te schrijven.

my $nieuw = "Nieuwe scores:
Paul 10
Paulie 9
Paulo 11";

spurt "nieuwescores.txt", $nieuw;

Nadat je de bovenstaande code hebt uitgevoerd, bestaat er een bestand nieuwescores.txt . Dat zal dan de nieuwe scores bevatten.

6.3.3. lines

lines kan worden gebruikt om gegevens regel voor regel uit een bestand te lezen.

Laten we hetzelfde bestand datafile.txt lezen dat we ook met slurp hebben gebruikt.

for "datafile.txt".IO.lines -> $line {
    say "Score: $line";
}
Uitvoer
Score: John 9
Score: Johnnie 7
Score: Jane 8
Score: Joanna 7

.IO verandert de string met de naam van een bestand in een object dat een bestand representeert.
.lines geeft de inhoud van het bestand regel voor regel, en de for lus gaat die dan één voor één behandelen.

6.4. Werken met bestanden en directories

6.4.1. Wat er in een directory zit

Raku kan de inhoud van een directory ook direct tonen zonder dat er externe commando’s voor hoeven te worden uitgevoerd, net zoals in een van de vorige voorbeelden.

say dir;              # Laat bestanden/directories uit de huidige directory zien
say dir "/Documents"; # Laat bestanden/directories zien van de gegeven directory

6.4.2. Directories aanmaken en verwijderen

Tevens kun je ook nieuwe directories aanmaken en verwijderen.

mkdir "nieuwdir";
rmdir "nieuwdir";

mkdir maakt een nieuwe directory aan.
rmdir verwijdert een lege directory. Geeft een foutmelding terug indien niet leeg.

6.4.3. Bestandsinformatie

Je kunt ook kijken of een specifieke naam bestaat, en of het een bestand of een directory is:

Maak in de directory waar je dit script gaat uitvoeren een lege directory dir123 en een leeg bestand genaamd script123.raku

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

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

say "script123.raku".IO.f;
say "dir123".IO.f;

IO.e geeft terug of de naam bestaat.
IO.f geeft terug of het een bestand is.
IO.d geeft terug of het een directory is.

Gebruikers van Windows kunnen zowel de / als de \\ gebruiken om directories aan te maken
C:\\rakudo\\bin
C:/rakudo/bin
Zie https://docs.raku.org/type/IO voor meer informatie over invoer en uitvoer.

7. Subroutines

In dit hoofdstuk behandelen we hoe je subroutines definieert, hoe je waarden aan ze kunt sturen, en hoe je waarden terug kunt krijgen.

7.1. Definitie

Subroutines (ook wel subs of functies) zijn een manier om een stuk functionaliteit in een pakketje te stoppen.

De definitie van een subroutine begint met het sleutelwoord sub. Na de definitie kun je het aanroepen met de naam die je het gegeven hebt.
Bekijk onderstaand voorbeeld:

sub buitenaardse-groet {
    say "Hallo aardlingen";
}

buitenaardse-groet;

Het vorige voorbeeld laat een subroutine zien die geen invoer nodig heeft.

7.2. Signatuur

Veel subroutines hebben een vorm van invoer nodig om hun werk te kunnen doen. Die invoer wordt gegeven door argumenten. Een subroutine mag 0 of meer parameters definiëren. Het aantal en het type van de parameters die door een subroutine worden gedefinieerd, noemen we de signatuur.

Onderstaande subroutine accepteert een string argument.

sub zeg-hallo (Str $naam) {
    say "Hallo " ~ $naam ~ "!!!!"
}
zeg-hallo "Paul";
zeg-hallo "Paula";

7.3. Selectie bij aanroep

Het is mogelijk om meer dan één subroutine met dezelfde naam, maar met een verschillende signatuur, te definiëren.

Op het moment dat de subroutine wordt aangeroepen, zal de uitvoerder besluiten welke versie van de subroutine werkelijk zal worden aangeroepen, afhankelijk van het aantal en het type van de gegeven argumenten. Dit soort subroutines wordt op dezelfde manier gedefinieerd als normale subroutines, maar in plaats van sub worden ze gedefinieerd met multi.

multi groet($naam) {
    say "Goede morgen $naam";
}
multi groet($naam, $titel) {
    say "Goede morgen $titel $naam";
}

groet "Jan";
groet "Laura", "Mevr.";

7.4. Default en Optionele Parameters

Als een subroutine is gedefinieerd om een argument te accepteren en we roepen het aan zonder dat benodigde argument, dan zal er een fout optreden.

Als alternatief biedt Raku de mogelijk om subroutines te definiëren met:

  • Optionele Parameters

  • Parameters met een verstek (default) waarde

7.4.1. Optionele parameters

Je kunt een optionele parameter aangeven door een ? achter de naam te plaatsen.

sub zeg-hallo($naam?) {
    with $naam { say "Hallo " ~ $naam }
    else { say "Hallo Mens" }
}
zeg-hallo;
zeg-hallo("Laura");

Als de gebruiker een bepaald argument niet meegeeft, dan wordt de eventuele default waarde van de parameter gebruikt.
Dit wordt aangegeven door een waarde toe te wijzen aan de parameter in de definitie van de subroutine.

sub zeg-hallo($naam = "Mens") {
    say "Hallo " ~ $naam;
}
zeg-hallo;
zeg-hallo("Laura");

7.5. Genoemde parameters

De parameters die we tot nu toe hebben gezien, worden "positionele parameters" genoemd. Arguments worden toegekend aan parameters door hun positie: het eerst argument gaat naar de eerste parameter, het tweede argument naar de tweede parameter, en zo verder.

Een parameter die toegewezen wordt aan de hand van een naam, heet een "genoemde parameter".

sub groet(:$naam, :$titel) {
    say "Goede morgen $titel $naam";
}

groet(naam => "Laura", titel => "Mrs.");
groet(titel => "Mr.", naam => "Pietje");
Uitvoer
Goed morgen Mrs. Laura
Goed morgen Mr. Pietje

Since the arguments are matched by name, they can be supplied in any order. Omdat de argument per naam toegekend worden aan de parameter, maakt het niet uit in welke volgorde ze worden gespecificeerd.

Als de naam van een variabele overeenkomt met de naam van een genoemde parameter, dan kan de aanroep verkort worden: :$naam in plaats van name => $naam

sub groet(:$naam, :$titel) {
    say "Goede morgen $titel $naam";
}

my $name = "Laura";
my $title = "Mrs.";
groet(naam => $naam, titel => $titel);   # regular
groet(:$naam, :$titel);                  # shorthand

7.6. Slurpende parameters

Een subroutine kan ook een variabel aantal argumenten opvangen in een slurpende parameter.
Een slurpende parameter is een array specificatie na een asterisk, zoals *@namen. Het verzamelt alle overige (positionele) argumenten.

sub groet-allen(*@namen) {
    for @namen -> $naam {
        say "Hallo $naam";
    }
}

groet-allen("Paul");
groet-allen("Paul", "Paula", "Laura");
Uitvoer
Hallo Paul
Hallo Paul
Hallo Paula
Hallo Laura

7.7. Beperkingen

We hebben eerder al gezien dat een parameter beperkt kan worden tot een type, zoals Str $naam.
Het kan nog verder worden beperkt door het sleutelwoord where toe te voegen, gevolgd door een conditie.

sub register(Int $leeftijd where $leeftijd >= 18) {
   say "Je bent registreerd";
}

register(21);
register(15);
Uitvoer
Je bent geregistreerd
Constraint type check failed in binding to parameter '$leeftijd'; expected anonymous constraint to be met but got Int (15)

De eerste aanroep is succesvol. De tweede niet omdat 15 weliswaar een Int is, maar die waarde voldoet niet aan de conditie.

7.8. Teruggeven van waarden

Alle subroutines die we tot nu toe hebben gezien doen iets en laten dan het resultaat op het scherm zien.

Ook al is dit heel normaal, soms willen we dat een subroutine een waarde teruggeeft dat we later in het programma kunnen gebruiken.

7.8.1. Impliciete teruggave

Onder normal omstandigheden is de waarde van de laatste regel van een subroutine de waarde die door de subroutine terug wordt gegeven.

sub kwadrateer ($x) {
    $x ** 2;
}
say "7 gekwadrateerd is gelijk aan " ~ kwadrateer(7);

7.8.2. Expliciete teruggave

Voor de duidelijkheid is het wellicht een goed idee om expliciet aan te geven wat we terug willen geven. Dit kunnen we doen met het return sleutelwoord.

sub kwadrateer ($x) {
    return $x ** 2;
}
say "7 gekwadrateerd is gelijk aan " ~ kwadrateer(7);

7.8.3. Beperken van mogelijke teruggegeven waarden

In een van de vorige voorbeelden hebben we gezien dat we parameters kunnen beperken tot een bepaald type. Hetzelfde kan worden gedaan met waarden die we teruggeven.

Om de teruggeven waarde te beperken tot een bepaald type, kunnen we de pijlnotatie --> in de signatuur gebruiken.

Aangeven van type van teruggave
sub kwadrateer ($x --> Int) {
    return $x ** 2;
}
say "1.2 gekwadrateerd is gelijk aan " ~ kwadrateer(1.2);

Als we de waarde voor teruggave niet van het juiste type is, zal er een foutmelding worden geproduceerd.

Type check failed for return value; expected Int but got Rat (1.44)

We kunnen niet alleen de typebeperking van de teruggeven waarde controleren; we kunnen ook laten controleren of het gedefinieerd is.

In het vorige voorbeeld gaven we aan dat de teruggegeven waarde een Int most zijn, zonder iets te zeggen over het wel of niet gedefinieerd zijn. We zouden ook hebben kunnen aangeven dat de teruggegeven Int wel of niet gedefinieerd moet zijn, met de volgende signaturen:
-→ Int:D en -→ Int:U

Het is een goede gewoonte om dit soort typebeperkingen te gebruiken.
Hieronder is een aangepaste versie van het vorige voorbeeld die :D gebruikt om aan te geven dat de teruggegeven Int gedefinieerd moet zijn.

sub kwadrateer ($x --> Int:D) {
    return $x ** 2;
}
say "1.2 gekwadrateerd is gelijk aan " ~ kwadrateer(1.2);
Zie https://docs.raku.org/language/functions voor meer informatie over subroutines en functies.

8. Functioneel Programmeren

In dit hoofdstuk gaan we kijken naar een aantal functionaliteiten die men kan gebruiken voor Functioneel Programmeren.

8.1. Functies zijn ook objecten.

Functies en subroutines zijn ook objecten, net als alle andere:

  • Ze kunnen worden doorgegeven als argument aan een subroutine

  • Ze kunnen worden teruggegeven als waarde door een subroutine

  • Ze kunnen worden toegekend aan een variabele

Een prachtig voorbeeld om dit concept te demonstreren is de map functie.
map is een zogenaamde hogere orde functie, want het accepteert een andere functie als argument.

Script
my @array = <1 2 3 4 5>;
sub kwadrateer($x) {
    $x ** 2
}
say map(&kwadrateer,@array);
Uitvoer
(1 4 9 16 25)
Uitleg

We hebben een subroutine kwadrateer gedefinieerd die het argument tot de tweede macht verheft.
Vervolgens hebben we map aangeroepen met twee argumenten: een subroutine en een array.
Het resultaat is een lijst waaroin alle elementen van het array zijn gekwadrateerd.

Merk op dat als we een subroutine als argument willen doorgeven, we een & voor de naam moeten zetten.

8.2. Anonieme Functies

Een anonieme functie wordt ook wel een lambda genoemd.
Een anonieme functie is niet bekend onder een naam (want die heeft het niet).

8.2.1. Puntig blok

Laten we het map voorbeeld herschrijven met een anonieme functie

my @array = <1 2 3 4 5>;
say map(-> $x { $x ** 2 },@array);

Merk op dat in plaats van een subroutine te declareren en het door middel van de naam als argument aan map door te geven, we het direct in de aanroep definiëren.
De anonieme subroutine -> $x { $x ** 2 } kan niet als zodanig worden aangeroepen want het heeft geen naam.

In Raku noemen we deze notatie een "puntig blok" (pointy block).

8.2.2. Kortere specificatie

Een puntig blok dat maar 1 argument accepteert, kan op twee manier korter worden geschreven.

Een blok zonder → specificatie ontvangt één argument in de verstek-variabele $_

my @array = <1 2 3 4 5>;
say map({ $_ ** 2 }, @array);

Men kan ook een asterisk gebruiken om het argument aan te geven: in dat geval zijn de accolades niet nodig.
* ** 2 is een kortere specificatie van { $_ ** 2 }

my @array = <1 2 3 4 5>;
say map(* ** 2, @array);

Alledrie de notaties geven hetzelfde resultaat: (1 4 9 16 25)

8.2.3. Opslaan in een variabele

Een puntig blok kan ook aan een variabele worden toegekend:

my $kwadrateer = -> $x {
    $x ** 2
}
say $kwadrateer(9);

8.3. Aankoppelen

In Raku kun je methoden aan elkaar koppelen, zodat je niet langer het resultaat van de aanroep van de ene subroutine als een argument aan een andere hoeft te geven.

Laten we het geval bekijken waarbij we een array met waarden kregen. Je wordt gevraagd om alle unieke (maar een keer voorkomende) waarden uit het array te halen en te sorteren van hoog naar laag.

Je zou dit probleem kunnen oplossen door iets te schrijven als:

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

Eerst roepen we de unique functie aan op @array. Het resultaat daarvan geven we als argument aan sort en dan geven we het resultaat daarvan door aan reverse.

In tegenstelling tot bovenstaand voorbeeld mag je methodes aan elkaar koppelen in Raku.
Bovenstaand voorbeeld kan dus als volgt worden geschreven, waarbij we gebruik maken van het aankoppelen van methoden (method chaining):

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

Je kunt zien dat het aankoppelen van methoden veel beter leest.

8.4. Aanvoer Operator

De aanvoer operator, ook wel pipe genoemd in sommige functionele programmeertalen, geeft een nog beter visualisatie van het aankoppelen van methoden.

Voorwaardse Aanvoer
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
@array ==> unique()
       ==> sort()
       ==> reverse()
       ==> my @resultaat;
say @resultaat;
Uitleg
Start met `array` en geef een lijst van unieke elementen
                  en sorteer dat
                  en keer de volgorde om
                  en sla het resultaat daarvan op in @resultaat

Zoals je kunt zien is de volgorde van de aanroepen van de methoden van voor naar achter.

8.4.1. Achterwaardse Aanvoer

my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @resultaat <== reverse()
              <== sort()
              <== unique()
              <== @array;
say @resultaat;
Uitleg

De achterwaardse aanvoer is net als de voorwaardse aanvoer, maar dan andersom.
De volgorde van de aanroepen van de methoden is van achteren naar voren.

8.5. Hyper Operator

De hyper operator >>. roept de methode aan op elke element van een lijst en geeft een lijst terug met het resultaat van die aanroepen.

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

say @array>>.is-prime;
say @array>>.&is-even;

Met de hyper operator kunnen we methoden aanroepen die standaard al in Raku zijn gedefinieerd, zoals is-prime dat ons vertelt of een getal een priemgetal is of niet.
Daarnaast kun je nieuwe subroutines definiëren en ze aanroepen met de hyper operator. In dat geval moeten we een & voor de naam plaatsen, bijvoorbeeld is-even

Dit is erg practisch want daardoor hoeven we geen for lus te schrijven om over elk element te itereren.

Raku garandeert dat de volgorde van de resultaten hetzelfde is als de volgorde van de originele waarden.
Maar er is geen garantie dat Raku de methoden in de zelfde volgorde of in dezelfde thread zal uitvoeren.
Men moet dus voorzichtig zijn bij het aanroepen van methoden met neveneffecten, zoals say of print (waarbij het neveneffect het tonen van de waarden is).

8.6. Juncties

Een junctie is een logische superpositie van waarden.

In onderstaand voorbeeld is 1 | 2 | 3 een junctie.

my $var = 2;
if $var == 1 | 2 | 3 {
    say "De variabele is 1 of 2 of 3"
}

Het gebruik van juncties zorgt meestal voor zogenaamde autothreading; een opdracht wordt voor elk element van de junctie uitgevoerd en de resultaten daarvan worden in een nieuwe junctie opgeslagen en teruggegeven.

8.7. Luie Lijsten

Een luie lijst is een lijst die lamlendig wordt geëvalueerd.
Lamlendig evalueren stelt de evaluatie van een expressie uit totdat deze echt nodig is en probeert herhaalde evaluaties te voorkomen door resultaten op te slaan.

De voordelen zijn:

  • Verbeterde prestaties doordat onnodige berekeningen worden vermeden

  • De mogelijkheid om potentieel oneindige datastructuren aan te maken

  • De mogelijkheid om de besturingsstroom te definiëren

Om een luie lijst te maken gebruiken we de …​ infix operator.
Een luie lijst heeft een of meer startelementen, een generator en een eindpunt.

8.7.1. Simpele luie lijst

my $luielijst = (1 ... 10);
say $luielijst;

Het start element is 1 en het eindpunt is 10. Er is geen generator aangegeven, dus de default generator wordt gebruikt (+1).
In andere woorden, deze luie lijst zal de volgende waarden teruggeven (als daarom wordt gevraagd): (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

8.7.2. Oneindige luie lijst

my $luielijst = (1 ... Inf);
say $luielijst;

Deze luie lijst kan een getal tussen 1 en oneindig geven, in andere woorden alle natuurlijke getallen.

8.7.3. Luie lijst met een afgeleide generator

my $luielijst = (0,2 ... 10);
say $luielijst;

De startelementen zijn 0 en 2 en het eindpunt is 10. Er is geen generator aangegeven, maar door de aangegeven startelementen kan Raku deduceren dat de generator (+2) is
Deze luie lijst zal de volgende waarden teruggeven (als daarom wordt gevraagd): (0, 2, 4, 6, 8, 10)

8.7.4. Luie lijst met een specifieke generator

my $luielijst = (0, { $_ + 3 } ... 12);
say $luielijst;

In dit voorbeeld specificeren we expliciet een generator tussen { }
Deze luie lijst zal de volgende waarden teruggeven (als daarom wordt gevraagd): (0, 3, 6, 9, 12)

Als men een expliciete generator specificeert moet het eindpunt één van de waarden zijn die door de generator terug kan worden gegeven.
Als we in bovenstaand voorbeeld het eindpunt vervangen door 10 in plaats van 12, dan zal de generator nooit stoppen. De generator springt dan over het eindpunt.

In plaats van 0 …​^ * > 10 kun je ook 0 …​ 10 schrijven
Je kunt dat lezen als: vanaf 0 tot de eerste waarde die groter is dan 10 (maar sluit die dan uit)

Deze generator zal nooit stoppen
my $luielijst = (0, { $_ + 3 } ... 10);
say $luielijst;
Deze generator zal wel stoppen
my $luielijst = (0, { $_ + 3 } ...^ * > 10);
say $luielijst;

8.8. Omsluitingen

Alle code objecten in Raku zijn zogenaamde "omsluitingen" (closures), hetgeen betekent dat ze kunnen refereren aan lokale variabelen die zichtbaar zijn buiten het directe eigen bereik.

sub maak-begroeting {
    my $naam = "Jan Jansen";
    sub begroeting {
        say "Goede morgen $naam";
    };
    return &begroeting;
}
my $groeter = maak-begroeting;
$groeter();

Indien je het bovenstaande programmaatje uitvoert, dan zal het Goede morgen Jan Jansen op het scherm tonen.
Dit is een tamelijk simpel voorbeeld. Het interessante aan dit voorbeeld is dat de begroeting (binnenste) subroutine teruggegeven were door de buitenste subroutine voordat het uitgevoerd werd.

$groeter is nu een omsluiting (closure) geworden.

Een omsluiting is een speciaal soort object dat twee zaken combineert:

  • Een subroutine

  • De omgeving waarin deze subroutine werd aangemaakt.

Die omgeving bestaat uit elke lokale variabele die "zichtbaar" (in-scope) was op het moment dat de closure werd aangemaakt. In dit geval, $groeter is een closure waarin zich zowel de begroeting subroutine bevat als ook de Jan Jansen string die bestond toen de closure werd aangemaakt.

Laten we eens naar een interessanter voorbeeld kijken:

sub groeter-generator($periode) {
    return sub ($naam) {
        return "Goede $periode $naam"
    }
}
my $smorgens = groeter-generator("morgen");
my $savonds  = groeter-generator("avond");

say $smorgens("Jan");
say $savonds("Jane");

In dit voorbeeld hebben we een subroutine groeter-generator($periode) gemaakt die één enkel argument $periode verwacht, en een nieuwe subroutine teruggeeft. Deze subroutine verwacht één enkel argument $naam waarmee de begroeting wordt geconstrueerd die wordt teruggegeven.

In feite is groeter-generator een subroutine fabriek. In dit voorbeeld gebruiken we deze groeter-generator om twee nieuwe subroutines aan te maken. De ene zegt Goede morgen, en de andere zegt Goede avond.

$smorgens en $savonds zijn allebei closures. Ze delen dezelfde subroutine definitie, maar hebben verschillende omgevingen.
In de omgeving van $smorgens heeft $periode de waarde morgen. In de omgeving van $savonds heeft $periode de waarde avond.

9. Klassen en Objecten

In het vorige hoofdstuk hebben we geleerd hoe Raku het gemakkelijker maakt om Functioneel te Programmeren.
In dit hoofdstuk gaan we kijken naar het object georiënteerd programmeren in Raku.

Object Georiënteerd Programmeren is een van de programmeerstijl paradigma’s die tegenwoordig veel wordt gebruikt.
Een object is een verzameling van variabelen en subroutines die bij elkaar gevoegd zijn.
De variabelen noemen we attributen en de subroutines noemen we methoden.
Attributen bepalen de staat van een object, methoden bepalen het gedrag van het object.

Een klasse definieert de structuur van een verzameling objecten.

Bekijk onderstaand voorbeeld om deze relatie beter te begrijpen:

Er bevinden zich 4 personen in een kamer

objecten ⇒ 4 personen

Deze 4 personen zijn menselijk

klasse ⇒ Mens

Ze hebben een allen een naam, leeftijd, geslacht en nationaliteit

attributen ⇒ naam, leeftijd, geslacht en nationaliteit

In het taalgebruik van object georiënteerd programmeren zeggen we dat de objecten instanties zijn van een klasse.

9.1. Definiëren van een klasse

Bekijk het onderstaande script:

class Mens {
    has $.naam;
    has $.leeftijd;
    has $.geslacht;
    has $.nationaliteit;
}

my $jan = Mens.new(naam => 'Jan', leeftijd => 23, geslacht => 'M', nationaliteit => 'Nederlands');
say $jan;

Met het class sleutelwoord definieert men een klasse.
Met het has sleutelwoord definieert men een attribuut van een klasse.
De .new() methode wordt een constructeur genoemd. Het maakt een object als een instantie van de klasse waarop het wordt aangeroepen.

In het bovenstaande script, bevat de variabele $jan de nieuwe instantie van "Mens" aangemaakt met Mens.new().
De argumenten op naam die we aan de .new() methode meegeven, worden gebruikt om de attributen van het nieuwe object te initialiseren.

We kunnen een klasse ook een lexicaal gebied geven door middel van my:

my class Mens {

}

9.2. Inkapseling

Inkapseling is een concept uit het object georiënteerd programmeren waarmee we de bundeling van data en methoden aangeven.
De gegevens (attributen) binnen een object zouden privé moeten zijn. In andere woorden, alleen maar toegankelijk vanaf binnen in het object.
Om toegang te verlenen aan de attributen vanaf buiten het object, gebruiken we methoden die we accessors noemen.

Onderstaande scripts geven hetzelfde resultaat.

Directe toegang tot een variabele
my $var = 7;
say $var;
Inkapseling
my $var = 7;
sub geef-var {
    $var;
}
say geef-var;

De subroutine geef-var kan men als een accessor beschouwen. Het maakt het mogelijk om aan de waarde van de variabele te komen zonder er direct toegang daarvoor nodig te hebben.

Inkapseling wordt in Raku mogelijk gemaakt door middel van zogenaamde twigils.
Twigils zijn secondaire sigils. Ze worden tussen de sigil en de naam van het attribuut geplaatst.
Bij klassen kunnen twee sigils worden gebruikt:

  • ! om aan te geven dat een attribuut privé is.

  • . om aan te geven dat een accessor voor de attribuut aangemaakt moet worden.

Als men geen twigil meegeeft, is een attribuut privé. Voor de leesbaarheid is het een goede gewoonte om in zo’n geval altijd de ! twigil te gebruiken.

Met wat we zojuist gezien hebben, zouden we bovenstaande klasse moeten herschrijven als:

class Mens {
    has $!naam;
    has $!leeftijd;
    has $!geslacht;
    has $!nationaliteit;
}

my $jan = Mens.new(naam => 'Jan', leeftijd => 23, geslacht => 'M', nationaliteit => 'Nederlands');
say $jan;

Voeg het volgende commando to: say $jan.leeftijd
Het zal de volgende fout geven: Method 'leeftijd' not found for invocant of class 'Mens'
De reden hiervoor is dat $!leeftijd privé is en daardoor alleen maar binnen het object gebruikt kan worden. Pogingen om aan het attribuut te komen van buiten het object zal je een foutmelding geven.

Vervang nu has $!leeftijd with has $.leeftijd en bekijk dan het resultaat van say $jan.leeftijd;

9.3. Genoemde en positionele argumenten

Alle klassen hebben beschikking over een .new() constructeur door overerving.
Het kan worden gebruikt om objecten aan te maken door het argumenten te geven.
De default constructeur accepteert alleen genoemde argumenten.
Als je bovenstaande voorbeelden bekijkt, dan zul je zien dat alle argumenten die we aan .new() hebben gegeven, op naam zijn:

  • naam => 'Jan'

  • leeftijd => 23

Wat als ik niet telkens de naam van elk attribuut wil meegeven als ik een nieuw object wil maken?
In dat geval moeten we een andere constructeur maken die positionele argumenten accepteert.

class Mens {
    has $.naam;
    has $.leeftijd;
    has $.geslacht;
    has $.nationaliteit;
    #nieuwe constructeur die de default constructeur vervangt voor deze klasse
    method new ($naam, $leeftijd, $geslacht, $nationaliteit) {
        self.bless(:$naam, :$leeftijd, :$geslacht, :$nationaliteit);
    }
}

my $jan = Human.new('Jan',23,'M','Nederlands');
say $jan;

9.4. Verplichte attributen en attributen met verstekwaarde

We hebben eerder al gezien dat een subroutine niet aangeroepen kan worden zonder een van haar parameters. De standaard constructor gedraagt zich anders: een attribuut waarvoor geen genoemd argument is meegegeven in een aanroep aan .new() blijft simpelweg gezien leeg.

Net als met parameters aan een subroutine, is het mogelijk om een verstekwaarde aan een attribuut toe te kennen.
Like the parameters of a subroutine, an attribute can be given a default value.
Dit gebeurt door een toewijzing aan te geven bij de definitie. Andersom, als er altijd een genoemd argument moet worden gegeven voor een attribuut, dan kan dat worden aangeven met de sleutelwoorden is required

class Mens {
    has $.naam is required;
    has $.nationaliteit = 'Onbekend';
}

my $jan = Menw.new(name => 'Jan');
say $jan.nationality;

my $niemand = Human.new;
Uitvoer
Onbekend
The attribute '$!naam' is required, but you did not provide a value for it.

The nationaliteit van $jan werd niet aangegeven, dus werd de verstekwaarde gebruikt.
$niemand kon niet worden gemaakt omdat het genoemde argument naam ontbrak.

9.5. Methoden

Methoden zijn de subroutines van een object.
Zij zijn, net als subroutines, een manier om een aantal functies te bundelen, ze accepteren argumenten, hebben een signatuur en kunnen worden gedefinieerd als multi.

Je definieert een methode met het method sleutelwoord.
Normaal gesproken zijn methoden nodig om bepaalde acties op de attributen van een object uit te voeren. Dit bekrachtigt het concept van inkapseling. De attributen van een object kunnen alleen worden gemanipuleerd vanaf binnen de klasse van het object door gebruik van methoden. De buitenwereld kan alleen maar gebruik maken van de methoden van het object en heeft geen toegang tot de attributen van het object.

class Mens {
    has $.naam;
    has $.leeftijd;
    has $.nationaliteit;
    has $.geschiktheid;
    method bepaal-geschiktheid
        if self.leeftijd < 21 {
            $!geschiktheid = 'Nee'
        } else {
            $!geschiktheid = 'Ja'
        }
    }
}

my $jan = Mens.new(naam => 'Jan', leeftijd => 23, nationaliteit => 'Nederlands');
$jan.bepaal-geschiktheid;
say $jan.geschiktheid;

Door een methode te definiëren in een klasse, kun je deze aanroepen op een object met de punt notatie:
object . methode of als in bovenstaand voorbeeld: $jan.bepaal-geschiktheid

Binnen de definitie van een methode kunnen we het sleutelwoord self gebruiken om te refereren aan het object zelf om andere methodes aan te roepen.

Binnen de definitie van een methode kunnen we direct een attribuut gebruiken door de ! twigil te gebruiken, zelfs als het attribuut is gedefinieerd met de . twigil.
De reden daarachter is dat wat de . twigil doet is een attribuut met ! te definiëren en automatisch te zorgen voor het aanmaken van een accessor.

In bovenstaand voorbeeld hebben if self.leeftijd < 21 en if $!leeftijd < 21 hetzelfde effect, maar technisch gezien zijn ze verschillend:

  • self.leeftijd roept de .leeftijd methode (accessor) aan
    Hetgeen overigens ook geschreven can worden als $.leeftijd

  • $!leeftijd is een directe toegang tot de variabele

9.5.1. Privé methoden

Normale methoden kunnen op objecten buiten de klasse zelf worden aangeroepen.

Privé methoden zijn methoden die alleen maar kunnen worden aangeroepen binnen een klasse.
Dit kan bijvoorbeeld gebruikt worden als een methode een andere methode moet aanroepen voor een specifieke actie. De methode die gebruikt kan worden vanaf de buitenwereld is publiek, terwijl de andere methode onzichtbaar moet blijven. Aangezien we niet willen dat gebruikers die methode direct kunnen aanroepen, definiëren we het als een privé methode.

Door een ! voor de naam van een methode te plaatsen, geven we aan dat het om een privé methode gaat. Privé methoden moeten worden aangeroepen met een ! in plaats van met .

method !ikbenprivé {
    #code waar het over gaat
}

method ikbenpubliek {
    self!ikbenprivé;
    #doe extra dingen
}

9.6. Klasse Attributen

Klasse Attributen zijn attributen die bij de klasse zelf horen en niet bij de objecten van die klasse.
Zij kunnen worden geïnitialiseerd bij de definitie.
Klasse attributen worden gedefinieerd met my in plaats van met has.
Zij worden aangeroepen op de klasse zelf in plaats van op haar objecten.

class Mens {
    has $.naam;
    my $.aantal = 0;
    method new($naam) {
        Mens.aantal++;
        self.bless(:$naam);
    }
}
my $a = Mens.new('a');
my $b = Mens.new('b');

say Mens.aantal;

9.7. Toegangssoorten

In alle voorbeelden die we tot nu toe hebben gezien, hebben we accessors alleen maar gebruikt om informatie uit een object te halen.

Maar wat nu als we de waarde van een attribuut willen veranderen?
In dat geval moeten we die attribuut als lees/schrijf markeren met de sleutelwoorden is rw (read/write).

class Mens {
    has $.naam;
    has $.leeftijd is rw;
}
my $jan = Mens.new(naam => 'Jan', leeftijd => 21);
say $jan.leeftijd;

$jan.leeftijd = 23;
say $jan.leeftijd;

Als je bij de definitie niets aangeeft, wordt een attribuut als alleen lezen gedefinieerd, maar je kunt ook expliciet is readonly aangeven.

9.8. Overerving

Overerving is een concept van het object georiënteerd programmeren.

Wanneer men klassen aan het definiëren is, komt men er snel genoeg achter dat sommige attributen/methoden in vele klassen voorkomen.
Moeten we dus maar code gaan dupliceren?
NEE! We moeten gebruik maken van overerving.

Laten we aannemen dat we twee klassen willen definiëren, een klasse voor Mensen en een klasse voor Werknemers.
Mensen hebben twee attributen: naam en leeftijd.
Werknemers hebben 4 attributen: naam, leeftijd, bedrijf en salaris

Men zou geneigd kunnen zijn om de klassen als volgt te definiëren:

class Mens {
    has $.naam;
    has $.leeftijd;
}

class Werknemer {
    has $.naam;
    has $.leeftijd;
    has $.bedrijf;
    has $.salaris;
}

Hoewel bovenstaande code technisch gezien correct is, is het qua concept van lage kwaliteit.

Een betere manier om zoiets te schrijven is:

class Mens {
    has $.naam;
    has $.leeftijd;
}

class Werknemer is Mens {
    has $.bedrijf;
    has $.salaris;
}

Het is sleutelwoord definieert de overerving.
In het taalgebruik van object georiënteerd programmeren, zeggen we dat Werknemer een kind is van Mens, en dat Mens de ouder is van Werknemer.

Alle kinderklassen erven de attributen en methoden van de ouderklasse, zodat het niet nodig is om deze opnieuw te definiëren.

9.8.1. Overnemen

Klassen erven alle attributen en methoden van hun ouderklas.
Er zijn gevallen waarin we willen det de methode in een kinderklasse zich anders gedraagt als methode die geërfd is van de ouderklasse.
Om dit te bereiken, definiëren we die methode ook in de kinderklasse.
We noemen dit concept overnemen (overriding).

In het onderstaande voorbeeld wordt de method stel-jezelf-voor geërfd door de Werknemer klasse.

class Mens {
    has $.naam;
    has $.leeftijd;
    method stel-jezelf-voor {
        say 'Hoi, ik ben een mens en mijn naam is ' ~ self.naam;
    }
}

class Werknemer is Mens {
    has $.bedrijf;
    has $.salaris;
}

my $jan = Mens.new(naam =>'Jan', leeftijd => 23,);
my $jessica = Werknemer.new(naam =>'Jessica', leeftijd => 25, bedrijf => 'Bureco', salaris => 4000);

$jan.stel-jezelf-voor;
$jessica.stel-jezelf-voor;

Het overnemen werkt als volgt:

class Mens {
    has $.naam;
    has $.leeftijd;
    method stel-jezelf-voor {
        say 'Hoi, ik ben een mens en mijn naam is ' ~ self.naam;
    }
}

class Werknemer is Mens {
    has $.bedrijf;
    has $.salaris;
    method stel-jezelf-voor {
        say 'Hoi, ik ben een werknemer, mijn naam is ' ~ self.naam ~ ' en ik werk bij: ' ~ self.bedrijf;
    }
}

my $jan = Mens.new(naam =>'Jan', leeftijd => 23,);
my $jessica = Werknemer.new(naam =>'Jessica', leeftijd => 25, bedrijf => 'Bureco', salaris => 4000);

$jan.stel-jezelf-voor;
$jessica.stel-jezelf-voor;

Afhankelijk van de klasse van het object, zal de juiste methode worden aangeroepen.

9.8.2. Submethoden

Submethoden zijn een soort methoden die niet worden geërfd door kinderklassen.
Ze zijn alleen toegankelijk in de klasse waarin ze worden gedefinieerd.
Ze worden gedefinieerd met het submethod sleutelwoord.

9.9. Meervoudige Overerving

Meervoudige overerving is toegestaan in Raku. Een klasse kan van meer dan één andere klasse erven.

class staafdiagram {
    has Int @.staaf-waarden;
    method plot {
        say @.staaf-waarden;
    }
}

class lijndiagram{
    has Int @.lijn-waarden
    method plot {
        say @.lijn-waarden
    }
}

class combo-diagram is staafdiagram is lijndiagram {
}

my $verkocht  = staafdigram.new(staaf-waarden => [10,9,11,8,7,10]);
my $voorspeld = lijndiagram.new(lijn-waarden  => [9,8,10,7,6,9]);

my $verkocht-vs-voorspeld =
  combo-diagram.new(staaf-waarden => [10,9,11,8,7,10],
                    lijn-waarden  => [9,8,10,7,6,9]);
say "Verkocht:";
$verkocht.plot;
say "Voorspeld:";
$voorspeld.plot;
say "Verkocht vs Voorspeld:";
$verkocht-vs-voorspeld.plot;
Uitvoer
Verkocht:
[10 9 11 8 7 10]
Voorspeld:
[9 8 10 7 6 9]
Verkocht vs Voorspeld:
[10 9 11 8 7 10]
Uitleg

De combo-diagram klasse zou in staat moeten zijn om de twee reeksen van waarden, een van "verkocht" in een staafdiagram en een van "voorspeld" in het lijndiagram, te tonen.
Dat is de reden waarom we het hebben gedefinieerd als een kinderklasse van lijndiagram en staafdiagram.
Je hebt waarschijnlijk gemerkt dat het aanroepen van de methode plot op het combo-diagram niet het gewenste resultaat gaf. Slechts één reeks van waarden werd getoond.
Waarom gebeurde dit?
combo-diagram erft van lijndiagram en staafdiagram en beide hebben ze een methode die plot heet. Als we die methode op combo-diagram aanroepen zal Raku dit conflict proberen op te lossen door één van de geërfde methoden aan te roepen.

Correctie

Om ervoor te zorgen dat dit correct functioneert, hadden we een methode plot in de combo-diagram klasse moeten definiëren.

class staafdiagram {
    has Int @.staaf-waarden;
    method plot {
        say @.staaf-waarden;
    }
}

class lijndiagram{
    has Int @.lijn-waarden
    method plot {
        say @.lijn-waarden
    }
}

class combo-diagram is staafdiagram is lijndiagram {
    method plot {
        say @.staaf-waarden;
        say @.lijn-waarden;
    }
}

my $verkocht  = staafdigram.new(staaf-waarden => [10,9,11,8,7,10]);
my $voorspeld = lijndiagram.new(lijn-waarden  => [9,8,10,7,6,9]);

my $verkocht-vs-voorspeld =
  combo-diagram.new(staaf-waarden => [10,9,11,8,7,10],
                    lijn-waarden  => [9,8,10,7,6,9]);
say "Verkocht:";
$verkocht.plot;
say "Voorspeld:";
$voorspeld.plot;
say "Verkocht vs Voorspeld:";
$verkocht-vs-voorspeld.plot;
Uitvoer
Verkocht:
[10 9 11 8 7 10]
Voorspeld:
[9 8 10 7 6 9]
Verkocht vs Voorspeld:
[10 9 11 8 7 10]
[9 8 10 7 6 9]

9.10. Rollen

Een Rol is net als een klasse verzameling van attributen en methoden.

Rollen kunnen worden gedefinieerd met het sleutelwoord role en klassen die een rol willen "spelen" kunnen dat aangeven met het does sleutelwoord.

Laten we het voorbeeld van meervoudige overerving herschrijven met rollen:
role staafdiagram {
    has Int @.staaf-waarden;
    method plot {
        say @.staaf-waarden;
    }
}

role lijndiagram{
    has Int @.lijn-waarden
    method plot {
        say @.lijn-waarden
    }
}

class combo-diagram does staafdiagram does lijndiagram {
    method plot {
        say @.staaf-waarden;
        say @.lijn-waarden;
    }
}

my $verkocht  = staafdigram.new(staaf-waarden => [10,9,11,8,7,10]);
my $voorspeld = lijndiagram.new(lijn-waarden  => [9,8,10,7,6,9]);

my $verkocht-vs-voorspeld =
  combo-diagram.new(staaf-waarden => [10,9,11,8,7,10],
                    lijn-waarden  => [9,8,10,7,6,9]);
say "Verkocht:";
$verkocht.plot;
say "Voorspeld:";
$voorspeld.plot;
say "Verkocht vs Voorspeld:";
$verkocht-vs-voorspeld.plot;

Voer het bovenstaande script uit en je zult zien dat de resultaten hetzelfde zijn.

Nu zul je jezelf afvragen: als rollen net als klassen werken, wat is dan hun nut?
Om deze vraag te beantwoorden passen we het eerste script dat we gebruikten om meervodige overerving te laten zien, degene waarin we hadden vergeten om de plot methode te definiëren.

role staafdiagram {
    has Int @.staaf-waarden;
    method plot {
        say @.staaf-waarden;
    }
}

role lijndiagram{
    has Int @.lijn-waarden
    method plot {
        say @.lijn-waarden
    }
}

class combo-diagram does staafdiagram does lijndiagram {
}

my $verkocht  = staafdigram.new(staaf-waarden => [10,9,11,8,7,10]);
my $voorspeld = lijndiagram.new(lijn-waarden  => [9,8,10,7,6,9]);

my $verkocht-vs-voorspeld =
  combo-diagram.new(staaf-waarden => [10,9,11,8,7,10],
                    lijn-waarden  => [9,8,10,7,6,9]);
say "Verkocht:";
$verkocht.plot;
say "Voorspeld:";
$voorspeld.plot;
say "Verkocht vs Voorspeld:";
$verkocht-vs-voorspeld.plot;
Uitvoer
===SORRY!===
Method 'plot' must be resolved by class combo-diagram because it exists in multiple roles (lijndiagram, staafdiagram)
Uitleg

Als een klasse meer dan één rol doet en er daarbij een conflict optreedt, dan zal de compiler een foutmelding geven.
Dit is een veel veiligere aanpak dan meervoudige overerving waarbij conflicten niet als een probleem worden gezien en er bij de uitvoering zo maar iets gedaan wordt.

Rollen waarschuwen je wanneer er een conflict is.

9.11. Introspectie

Introspectie is het verkrijgen van informatie over de eigenschappen van een object, zoals het type, haar attributen of haar methoden.

class Mens {
    has $.naam;
    has $.leeftijd;
    method stel-jezelf-voor {
        say 'Hoi, ik ben een mens en mijn naam is ' ~ self.naam;
    }
}

class Werknemer is Mens {
    has $.bedrijf;
    has $.salaris;
    method stel-jezelf-voor {
        say 'Hoi, ik ben een werknemer, mijn naam is ' ~ self.naam ~ ' en ik werk bij: ' ~ self.bedrijf;
    }
}

my $jan = Mens.new(naam =>'Jan', leeftijd => 23,);
my $jessica = Werknemer.new(naam =>'Jessica', leeftijd => 25, bedrijf => 'Bureco', salaris => 4000);

say $jan.WHAT;
say $jessica.WHAT;
say $jan.^attributes;
say $jessica.^attributes;
say $jan.^methods;
say $jessica.^methods;
say $jessica.^parents;
if $jessica ~~ Mens { say 'Jessica is een Mens'};

Introspectie is mogelijk door:

  • .WHAT geeft de klasse van het object.

  • .^attributes geeft een lijst met attributen van het object.

  • .^methods geeft een lijst van alle methoden die op het object kunnen worden uitgevoerd.

  • .^parents geeft een lijst met alle ouderklassen van het object.

  • ~~ wordt de smart-match operator genoemd. Het geeft de waarde True als het object is aangemaakt met de gegeven klasse, of met een van de ouderklassen van de gegeven klasse.

Voor meer informatie over Object Georiënteerd Programmeren in Raku, bekijk dan:

10. Exception Handling

Exceptions zijn de uitzonderingsgevallen die optreden bij het uitvoeren van een programma op het moment dat er iets fout gaat.
We zeggen dat exceptions worden geworpen (thrown).

Bekijk onderstaand script dat correct kan worden uitgevoerd:

my Str $naam;
$naam = "Joanna";
say "Hallo " ~ $naam;
say "Hoe gaat het?"
Uitvoer
Hallo Joanna
Hoe gaat het?

Bekijk nu onderstaand script dat een exception werpt:

my Str $naam;
$naam = 123;
say "Hallo " ~ $naam;
say "Hoe gaat het?"
Uitvoer
Type check failed in assignment to $naam; expected Str but got Int
   in block <unit> at bestandsnaam.raku:2

Je zou hebben moeten zien dat zodra er een fout optreedt (in dit geval het toewijzen van een getal aan een string variabele) het programma stopt en de regels code na de fout niet worden uitgevoerd, zelfs als deze wel juist zijn.

10.1. Opvangen van Exceptions

Exception handling is het proces van het opvangen van de exceptions die worden geworpen om ervoor te zorgen dat het script blijft werken.

Er zijn twee manieren om een exception op te vangen op de plaatsen in je code waar je die verwacht.

10.1.1. try

Indien je alleen maar zeker wilt zijn dat een exception niet je programma beëindigt, en je bent niet geïnteresseerd in de reden, dan kun je die code omgeven in een try blok.

my Str $naam;
try {
    $naam = 123;
    say "Hallo " ~ $naam;
}
say "Hoe gaat het?"
Uitvoer
Hoe gaat het?

Indien de code in een try blok een exception werpt, dan wordt die exception opgevangen, en de waarde Nil wordt dan teruggegeven in plaats van de laatste waarde in het blok.

Het exception object wordt opgeslagen in the speciale variabele $!. Als er niets misging in het blok, dan krijgt deze de waarde Nil en het if blok wordt dan overgeslagen in onderstaand voorbeeld:

my Str $naam;
try {
    $naam = 123;
    say "Hello " ~ $naam;
}
if $! {
    say "Er is iets misgegaan: " ~ $!.message;
}
say "Hoe gaat het?";
Uitvoer
Er is iets misgegaan: Type check failed in assignment to $name; expected Str but got Int (123)
Hoe gaat het?

10.1.2. CATCH

De andere manier om exceptions op te vangen, is met een CATCH blok. De code in een CATCH blok wordt uitgevoerd zodra er een exception wordt geworpen in een blok waarin zich een CATCH blok bevindt. Elk blok kan maar één CATCH blok hebben.

Het CATCH blok kan op elke plaats in een blok geplaatst worden. Meestal is het het beste om dat aan het begin van een blok te doen, zoday iemand die de code moet lezen direct kan zien dat exceptions een speciale behandeling krijgen in dat blok.

try {
    CATCH {
        default {
            # deze code zal alleen uitgevoerd worden als een exception is geworpen
        }
    }
    # voer hier de code in
    # als er iets fout gaat, zal de code in het CATCH block uitgevoerd worden
    # als alles goed gaat, wordt de code in het CATCH block genegeerd
}

10.2. Types van exceptions

Het CATCH block kan worden gedefinieerd op dezelfde manier als een given block. Dat betekent dat we verschillende soorten van exceptions kunnen opvangen en afhandelen.

try {
    CATCH {
        when X::AdHoc { # doe iets als een exception van het type X::AdHoc is geworpen }
        when X::IO { # doe iets als een exception van het type X::IO is geworpen }
        when X::OS { # doe iets als een exception van het type X::OS is geworpen }
        default { # doe iets als een ander soort exception is geworpen }
    }
    # voer hier de code in
    # als er iets fout gaat, zal de code in het CATCH block uitgevoerd worden
    # als alles goed gaat, wordt de code in het CATCH block genegeerd
}

Alle getypeerde exceptions erven van de klasse Exception. De namen van de altijd beschikbare exception klassen beginnen met X::, hieronder zie je een paar voorbeelden:
X::AdHoc is het simpelste exception type
X::IO is gerelateerd aan IO fouten
X::OS is gerelateerd aan OS fouten
X::Str::Numeric wordt geworpen bij een fout in het veranderen van een string naar een getal.

Zie https://docs.raku.org/type-exceptions.html voor een complete lijst van exception types en de daarbij behorende methoden.

10.3. Uitleg van exceptions

Binnen een CATCH blok wordt de exception geplaatst in de verstek-variabele $_
Een aanroep van de .message methode geeft dan een bescrijving van de fout die het werpen van de exception veroorzaakte.

my Str $naam;
{
    CATCH {
        default {
            say "Er ging iets fout: " ~ .message;
        }
    }
    $naam = 123;
    say "Hallo " ~ $naam;
}
say "Hoe gaat het?";
Uitvoer
Er ging iets fout: Type check failed in assignment to $name; expected Str but got Int (123)
Hoe gaat het?

10.4. Het Werpen Van Exceptions

Naast het vangen van exceptions, laat Raku het ook toe om een exception expliciet te werpen.
Man kan twee soorten van exceptions werpen:

  • ad-hoc exceptions

  • getypeerde exceptions

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

Ad-hoc exceptions worden geworpen door de die subroutine gevolgd door de uitleg van de exception.

Getypeerde exceptions zijn objecten, daarom moeten we de .new() constructeur gebruiken in bovenstaand voorbeeld.

11. Reguliere Expressies

Een reguliere expressie, of regex, is een reeks van karakters die worden gebruikt voor patroonherkenning.
De gemakkelijkste manier om dit te begrijpen, is om het te beschouwen als een patroon.

if 'verlichting' ~~ m/ licht / {
    say "verlichting bevat het woord licht";
}

In dit voorbeeld werd de smart match operator ~~ gebruikt om te kijken of een string (verlichting) het woord (licht) bevat.
"Verlichting" wordt vergeleken met de regex m/ light /

11.1. Definiëren van een Regex

Een reguliere expressie kan als volgt worden gedefinieerd:

  • /licht/

  • m/licht/

  • rx/licht/

Tenzij het specifiek wordt aangeduid, is witruimte zonder betekenis, dus m/licht/ en m/ licht / zijn hetzelfde.

11.2. Vergelijken van karakters

Alphanumerieke karakters en het liggend streepje _ mogen als zichzelf worden geschreven.
Alle andere karakters moeten speciaal worden behandeld door er een backslash \\ voor te plaatsen of door ze te omgeven door aanhalingstekens.

Backslash
if 'Temperatuur: 13' ~~ m/ \: / {
    say "De string bevat een dubbele punt :";
}
Enkele aanhalingstekens
if 'Leeftijd = 13' ~~ m/ '=' / {
    say "De string bevat een is-gelijk teken =";
}
Dubbele aanhalingstekens
if 'naam@bedrijf.nl' ~~ m/ "@" / {
    say "Dit is een juist email-adres want het bevat een @ karakter";
}

11.3. Vergelijken van categorieën van karakters

Karakters kunnen worden geclassificeerd in categorieën en ook daarmee kunnen we vergelijken.
We kunnen ook vergelijken met de inverse van een categorie (alle karakters behalve degene in die categorie).

Categorie

Regex

Inverse

Regex

Woord karakter (letter, cijfer or liggend streepje)

\w

Alle karakters behalve een woord karakter

\W

Cijfer

\d

Elk karakter die geen cijfer zijn

\D

Witruimte

\s

Elk karakter dat geen witruimte is

\S

Horizontale witruimte

\h

Elk karakter dat geen horizontale witruimte is

\H

Verticale witruimte

\v

Elk karakter dat geen verticale witruimte is

\V

Tab

\t

Elk karakter behalve de Tab

\T

Nieuwe regel

\n

Elk karakter dat geen nieuwe regel aangeeft

\N

if "Jan123" ~~ / \d / {
    say "Deze naam is niet toegestaan want het bevat cijfers";
} else {
    say "Deze naam is toegestaan"
}
if "Jan-Met" ~~ / \s / {
    say "Deze string bevat witruimte";
} else {
    say "Deze string bevat geen witruimte"
}

11.4. Unicode eigenschappen

Het vergelijken van categorieën van karakters zoals we in de vorige sectie zagen, is erg gemakkelijk.
Maar wellicht is het gebruik van Unicode eigenschappen een meer systematische aanpak.
Unicode eigenschappen worden omgeven door <: >

if "Devanagari cijfers १२३" ~~ / <:N> / {
    say "Bevat een cijfer";
} else {
    say "Bevat geen enkel cijfer"
}
if "Привет, Иван." ~~ / <:Lu> / {
    say "Bevat een hoofdletter";
} else {
    say "Bevat geen enkele hoofdletter"
}
if "Jan-Met" ~~ / <:Pd> / {
    say "Bevat een streepje";
} else {
    say "Bevat geen enkel streepje"
}

11.5. Jokers

Jokers (wildcards) kunnen ook in een regex worden gebruikt.

De punt . betekent elk enkel karakter.

if 'abc' ~~ m/ a.c / {
    say "Komt overeen";
}
if 'a2c' ~~ m/ a.c / {
    say "Komt overeen";
}
if 'ac' ~~ m/ a.c / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}

11.6. Factoren

Factoren (quantifiers) komen na een karakter en worden gebruikt om aan te geven hoe vaak we dat karakter verwachten.

11.6.1. Nul of één keer

Het vraagteken ? betekent nul of één keer.

if 'ac' ~~ m/ a?c / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}
if 'c' ~~ m/ a?c / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}

11.6.2. Nul of meer keer

De asterisk * betekent nul of meer keer.

if 'az' ~~ m/ a*z / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}
if 'aaz' ~~ m/ a*z / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}
if 'aaaaaaaaaaz' ~~ m/ a*z / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}
if 'z' ~~ m/ a*z / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}

11.6.3. Één of meer keer

Het plusteken + betekent tenminste één keer.

if 'az' ~~ m/ a+z / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}
if 'aaz' ~~ m/ a+z / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}
if 'aaaaaaaaaaz' ~~ m/ a+z / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}
if 'z' ~~ m/ a+z / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}

11.7. Verankering

Een regex kan overal in een string overeenkomen. Ankers verbinden een overeenkomst aan een bepaalde positie in de string.

  • ^ verankert aan het begin van de string.

  • $ verankert aan het einde van de string.

if 'raku gids' ~~ m/ ^ raku / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}
if 'de raku gids' ~~ m/ ^ raku / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}
if 'de raku gids' ~~ m/ gids $ / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}

11.8. Alternatieven

Het | symbool ("vertical bar") betekent of. Het kan worden gebruikt om twee of meer alternatieven aan te geven.

if 'raku.org' ~~ m/ com | org | net / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}
if 'raku.rocks' ~~ m/ com | org | net / {
    say "Komt overeen";
} else {
    say "Komt niet overeen";
}

11.9. Resultaten van een Vergelijking

Elke keer wanneer het vergelijken van een string met een regex succesvol is, wordt het resultaat in een speciale variable $/ geplaatst.

Script
if 'Rakudo is een Raku compiler waarmee...' ~~ m/:s Raku compiler / {
    say "Het resultaat is: " ~ $/;
    say "De string vóór de gevonden string is: " ~ $/.prematch;
    say "De string achter de gevonden string is: " ~ $/.postmatch;
    say "De gevonden string begint op positie: " ~ $/.from;
    say "De gevonden string eindigt één positie voor: " ~ $/.to;
}
Uitvoer
Het resultaat is: 「Raku-compiler」
De string vóór de gevonden string is: Rakudo is een
De string achter de gevonden string is:  waarmee...
De gevonden string begint op positie: 14
De gevonden string eindigt één positie voor: 27
Uitleg

$/ geeft een Match Object terug (de string die gevonden is door de regex)
Deze methoden kunnen worden aangeroepen op een Match Object:
.prematch geeft de string vóór de gevonden string terug.
.postmatch geeft de string na de gevonden string terug.
.from geeft de positie van het eerste karakter van de gevonden string terug.
.to geeft de positie nà het laatste karakter van de gevonden string terug.

Witruimte heeft per default geen betekening in een regex.
Als we willen vergelijken met een regex die witruimte bevat, dan moeten we dat expliciet aangeven.
De :s in de regex m/:s Raku/ geeft aan dat witruimte niet moet worden genegeerd.
We zouden de regex ook kunnen hebben schrijven als m/ Raku \s compiler / waarbij we \s als een teken voor witruimte hebben gebruikt, zoals we eerder hebben gezien.
Als een regex meer dan één witruimte bevat, dan is het gebruik van :s gemakkelijker in plaats van \s te specificeren voor elke witruimte.

11.10. Uittreksels

Indien men een gedeelte van een regex omgeeft met haken, dan vangt men dat gedeelte van de string in een uittreksel.
Uittreksels zijn genummerd vanaf nul. Ze zijn beschikbaar in de variabelen $0, $1, $2 enzovoort.

Script
if 'Jan is 23 jaar' ~~ m/ (\w+) \s is \s (\d+) / {
    say "Naam: $0";
    say "Leeftijd: $1";
}
Uitvoer
Name: Jan
Leeftijd: 23
Uitleg

(\w+) is het eerste uittreksel. Het komt overeen met Jan en wordt in $0 opgeslagen
(\d+) is het tweede uittreksel. Het komt overeen met 23 en wordt in $1 opgeslagen

11.11. Substitutie

Een regex kan ook worden A regex can also be used to replace the part of a string that it matches.
This is done using the .subst method. It is given a regex and a replacement, and it returns a new string.

Script
my $tekst = 'Ik drink thee, thee is goed';
say $tekst.subst(/thee/, 'koffie');
say $tekst.subst(/thee/, 'koffie', :g);
say $tekst;
Uitvoer
Ik drink koffie, thee is goed
Ik drink koffie, koffie is goed
Ik drink thee, thee is goed
Uitleg

.subst vervangt alleen de eerste overeenkomst.
Door het toevoegen van :g (kort voor globaal) worden alle overeenkomsten vervangen.
.subst verandert de onderliggende string niet, zoals de laatste regel toont.

11.12. Voorbeeld

Laten we eens kijken of een email-adres goed is of niet.
Voor dit voorbeeld zullen we aannemen dat een werkend email-adres wordt gevormd door:
voornaam [punt] achternaam [bij] bedrijfsnaam [punt] top-niveau (e.g. nl)

De regex die in dit voorbeeld wordt gebruikt voor het valideren van een email-adres is niet erg accuraat.
Het is alleen bedoeld om de functionaliteit van een regex in Raku te tonen.
Gebruik deze regex niet in deze vorm in productie.
Script
my $email = 'jan.met@raku.org';
my $regex = / ^ <:L>+ \. <:L>+ \@<:L+:N>+ \. (com|org|net) $ /;

if $email ~~ $regex {
    say $/ ~ " lijkt een valide email-adres";
} else {
    say "Dit email-adres kon niet gevalideerd worden";
}
Uitvoer

jan.met@raku.org lijkt een valide email-adres

Uitleg
  • <:L> accepteert een enkele letter

  • <:L>+ accepteert één of meer letters

  • \. accepteert een enkele [punt]

  • \@ accepteert een enkel @-karakter

  • <:L+:N> accepteert één of meer letters gevolgd door een cijfer

  • <:L+:N>+ accepteert één of meer letters + cijfer één of meer keer

  • (com|org|net) komt overeen met één van de drie alternatieven

  • ^ en $ zorgen ervoor dat niets voor of achter het email-adres mogen staan

De regex kan als volgt uit elkaar worden gehaald:

  • voornaam <:L>+

  • [punt] \.

  • achternaam <:L>+

  • [bij] \@

  • bedrijfsnaam <:L+:N>+

  • [punt] \.

  • com/org/net (com|org|net)

We kunnen een regex ook in aparte regexen op naam opdelen
my $email = 'jan.met@raku.org';
my regex veel-letters { <:L>+ };
my regex punt { \. };
my regex bij { \@ };
my regex veel-letters-cijfers { <:L+:N>+ };
my regex domein { com | org | net }

if $email ~~ / <veel-letters> <punt> <veel-letters> <bij> <veel-letters-nummers> <dot> <domein> / {
    say $/ ~ " lijkt een valide email-adres";
} else {
    say "Dit email-adres kon niet gevalideerd worden";
}

Een regex op naam wordt als volgt gedefinieerd: my regex regex-naam { regex definitie }
Een regex op naam kan als volgt worden aangeroepen: <regex-naam>

Zie https://docs.raku.org/language/regexes voor meer informatie over regexen.

12. Grammatica’s

In het vorige hoofdstuk hebben we een email regex onderverdeeld in meerdere regexen met een naam.
Een grammatica gaat verder op dit idee. Het groupeert regexen met een naam om de vorm van een gehele tekst te beschrijven.

Grammatica’s worden gebruikt om een tekst te ontleden: om te controleren dat het voldoet aan een bepaalde vorm, en om die delen waarin we geïntereseerd zijn er uit te halen.

12.1. Definiëren van een grammatica

Een grammatica wordt gedefinieerd met het sleutelwoord grammar. Regexen met een naam worden gedefinieerd met het sleutelwoord token.
Het token met de naam TOP is special: het beschrijft de vorm van de tekst als een geheel, gebruikmakend van de andere "tokens".

Laten we het email voorbeeld herschrijven als een grammatica.

grammar Email {
    token TOP        { <voornaam> '.' <achternaam> '@' <bedrijf> '.' <domein> }
    token voornaam   { <:L>+ }
    token achternaam { <:L>+ }
    token bedrijf    { <:L+:N>+ }
    token deomein    { com | org | net }
}
Uitleg

TOP definieert dat een email-adres bestaat uit een voornaam, een punt, een achternaam, een apestaartje en een domein.
De andere tokens definiëren hoe elk deel er uit ziet.
De punt en het apenstaartje worden omgeven door aanhalingstekens omdat ze geen alfanumerieke karakters zijn.

12.2. Parsing

A grammar is used by calling its .parse() method on a string. Een grammatica wordt uitgevoerd door het aanroepen van de .parse() methode met de string als argument

Script
grammar Email {
    token TOP        { <voornaam> '.' <achternaam> '@' <bedrijf> '.' <domein> }
    token voornaam   { <:L>+ }
    token achternaam { <:L>+ }
    token bedrijf    { <:L+:N>+ }
    token domein     { com | org | net }
}

my $email = 'jan.jansen@a2.org';

if Email.parse($email) {
    say "$email is een email-adres";
} else {
    say "$email is geen geldig email-adres";
}
Uitvoer
john.doe@route66.org is een email-adres

De aanroep .parse() is alleen succesvol indien de gehele string overeenkomt met TOP. Daarom hebben de ankers ^ en $, die we wél gebruikten in de regex, hier niet nodig.

Vervang nu the email-adres met jan.jansen@a2.xyz en voer heet script weer uit.
Het zal nu "jan.jansen@a2.zyx is geen geldig email-adres" tonen, omdat xyz niet een van de geldige domeinen is.

12.3. Extracting data

When it succeeds, .parse() returns a single match object, which we store in a scalar.
The part of the text that was matched by a token can be accessed using the name of that token, with the same angle brackets that are used with a hash.

Script
grammar Email {
    token TOP        { <voornaam> '.' <achternaam> '@' <bedrijf> '.' <domein> }
    token voornaam   { <:L>+ }
    token achternaam { <:L>+ }
    token bedrijf    { <:L+:N>+ }
    token domein     { com | org | net }
}

my $match = Email.parse('jan.jansen@a2.org');

say "Voornaam: " ~ $match<voornaam>;
say "Achternaam:" ~ $match<achternaam>;
say "Bedrijf: " ~ $match<bedrijf>;
say "Domein: " ~ $match<domein>;
Uitvoer
Voornaam: jan
Achternaam: jansen
Bedrijf: a2
Domein: org

Met één enkele definitie is een grammatica in staat om zowel het email-adres te valideren, en de gewenste informatie er uit te halen.

Voor meer informatie over grammatica’s, zie https://docs.raku.org/language/grammars

13. Raku Modules

Raku is een algemeen toepasbare programmeertaal. Het kan worden gebruikt om vele taken uit te voeren, zoals: manipulatie van tekst, plaatjes, web, databases, netwerkprotocollen, etc.

Herbruikbaarheid is een erg belangrijke eigenschap waardoor programmeurs niet telkens het wiel opnieuw hoeven uit te vinden als ze aan een nieuwe taak beginnen.

Raku maakt het mogelijk om modules aan te maken en die te distribueren. Elke module is een bundel van functionaliteit die kan worden gebruikt zodra deze is geïnstalleerd.

Zef is een programma om modules te beheren dat meestal ook wordt geïnstalleerd.

Om een specifieke module te installeren moet men onderstaand commando in een terminalvenster intypen:

zef install "module name"

Een overzicht van beschikbare Raku modules is te vinden op: https://raku.land/

13.1. Gebruiken van Modules

JSON is a text format for storing and exchanging data.

JSON is een tekst standaard voor het opslaan en uitwisselen van data.
Het wordt gebruikt door vele webdiensten en applicaties, waardoor vroeger of later jouw programma in staat zal moeten zijn om JSON te lezen, of te produceren.
Een JSON document bestaat uit naam/waarde paren en lijsten. Deze komen overeen met hashes en arrays in Raku.

Gelukkig hoef je niet zelf een JSON lezer te ontwikkelenl; er bestaat al een Raku module voor.

Laten we die installeren.
zef install JSON::Fast

En voer dan onderstaand script uit:

use JSON::Fast;

my %gebruiker = naam => 'Jan', leeftijd => 23;
my $json = to-json(%gebruiker);

say $json;
Uitvoer
{
  "naam": "Jan",
  "leeftrijd": 23
}

Om de to-json() functie, die een hash verandert in JSON, uit te kunnen voeren moeten we de benodigde module laden.
Het use sleutelwoord laadt de module, en levert zo een to-json subroutine die kan worden uitgevoerd.

Raku does not keep the keys of a hash in a fixed order, so age might come before name when you run the script. The order can change each time the script runs.
Raku houdt de namen in een hash niet in een bepaalde orde, dus leeftijd kan ook vóór naam worden getoond. En die volgorde kan ook verschillend zijn elke keer als dit script wordt uitgevoerd.

Dezelfde module levert ook een from-json() subroutine die het omgekeerde doet.

use JSON::Fast;

my $json = '{ "naam": "Jan", "leeftijd": 25 }';
my %gebruiker = from-json($json);

say %gebruiker<naam>;
say %gebruiker<leeftijd>;
Uitvoer
Jan
25
Voor meer informatie over modules, zie https://docs.raku.org/language/modules

14. Unicode

Unicode is een standaard voor het coderen en representeren van tekst die de meeste schriften van de wereld ondersteund.
UTF-8 is een manier van het coderen in Unicode van alle mogelijke karakters (ook wel "codepoints" genoemd).

Karakters zijn gedefinieerd door een:
Grafeem: Visuele voorstelling.
Codepoint: Een nummer toegewezen aan dat karakter.

14.1. Het gebruik van Unicode

14.1.1. Specificeren van karakters

Laten we eens zien hoe we met Unicode karakters kunnen laten zien
say "a";
say "\x0061";
say "\c[LATIN SMALL LETTER A]";

Bovenstaande 3 regels laten verschillende manieren zien om een karakter op te bouwen:

  1. Door het karakter (grafeem) direct te schrijven

  2. Door \x te gebruiken en het codepoint in hexadecimaal aan te geven

  3. Door \c te gebruiken en de naam van het codepoint aan te geven.

Laten we eens proberen om een smiley te tonen:
say "☺";
say "\x263a";
say "\c[WHITE SMILING FACE]";

14.1.2. Combineren van karakters

Voorbeeld van het gebruik van twee codepoints
say "á";
say "\x00e1";
say "\x0061\x0301";
say "\c[LATIN SMALL LETTER A WITH ACUTE]";

De letter á kan worden geschreven als:

  • door het unieke codepoint \x00e1 te gebruiken

  • door combinatie van codepoints voor a en acute \x0061\x0301

Sommige van de methoden die kunnen worden gebruikt:
say "á".NFC;
say "á".NFD;
say "á".uniname;
Uitvoer
NFC:0x<00e1>
NFD:0x<0061 0301>
LATIN SMALL LETTER A WITH ACUTE

NFC geeft het unieke codepoint.
NFD haalt het karakter uit elkaar en geeft de codepoints van elk onderdeel.
uniname geeft de naam van de codepoint.

14.1.3. Namen en berekeningen

Unicode letters kunnen ook gebruikt worden in namen:
my $Δ = 1;
$Δ++;
say $Δ;
Unicode kan ook worden gebruikt in berekeningen
my $var = 2 + ⅒;
say $var;

14.2. Operaties bekend met Unicode

14.2.1. Nummers

Arabische cijfers zijn de tien cijfers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Deze verzameling van cijfers wordt wereldwijd het meest gebruikt.

In sommige delen van de wereld worden soms andere verzamelingen van cijfers gebruikt.

Je hoeft niets speciaals te doen om cijfers uit deze andere verzamelingen te ondersteunen: alle methodes en operatoren werken zoals te verwachten valt.

say (٤,٥,٦,1,2,3).sort; # (1 2 3 4 5 6)
say 1 + ٩;              # 10

14.2.2. Strings

Indien we de algemene string operatoren gebruiken, kan het zijn dat we niet altijd het gewenste resultaat bereiken, met name bij het vergelijken en sorteren.

Vergelijking
say 'a' cmp 'B'; # More

Dit voorbeeld toont that a groter is dan B. De reden hiervoor is dat de numerieke waarde van het "code point" van een onderkast a hoger is dan de numerieke waarde van "code point" van een bovenkast B.

Hoewel dit technisch gezien juist is, is het waarschijnlijk niet waar we op uit waren.

Gelukkig heeft Raku methoden en operator die het Unicode Collation Algorithm implementeren.
Eén daarvan is unicmp dat zich net zo gedraagt als cmp, maar kijkt naar de unicode-eigenschappen.

say 'a' unicmp 'B'; # Less

Zoals je kunt zien geeft het gebruik van de unicmp operator het resultaat dat a kleiner is dan B.

Sorteren

De sort methode sorteert op basis van "code points". Raku geeft je ook een collate methode die sorteert volgens het Unicode Collation Algorithm.

say ('a','b','c','D','E','F').sort;    # (D E F a b c)
say ('a','b','c','D','E','F').collate; # (a b c D E F)
Voor meer informatie over Unicode, zie https://docs.raku.org/language/unicode

15. Parallelisme, Gelijktijdigheid en Asynchroniciteit

Raku has built-in features that let a program do several things at the same time.
In this chapter we will take a look at how to run code in parallel, and how to deal with tasks that take time to complete.

Raku heeft een aantal functies ingebouwd waarmee een programma een aantal dingen tegelijkertijd kan uitvoeren.
In dit hoofdstuk zullen we kijken hoe je code gelijktijdig kan laten uitvoeren, en hoe om te gaan met taken die langere tijd nodig hebben om tot een goed einde te komen.

15.1. Parallelisme

Onder normale omstandigheden worden alle taken in een programma achter elkaar uitgevoerd.
Dit hoeft niet noodzakelijkerwijs een probleem te zijn tenzij wat je probeert te doen een hoop tijd vergt.

Gelukkig heeft Raku een aantal mogelijkheden die het mogelijk maken om delen van een programma parallel uit te laten voeren.
Op dit moment is het belangrijk om op te merken dat parallelisme twee dingen kan betekenen:

  • Taak Parallelisme: Twee (of meer) onafhankelijke expressies die parallel worden uitgevoerd..

  • Data Parallelisme: Een enkele expressie die over een lijst van elementen parallel wordt uitgevoerd.

Laten we beginnen met de laatste.

15.1.1. Data Parallelisme

my @array = 0..50000;                        # vullen van het Array
my @resultaat = @array.map({ is-prime $_ }); # roep is-prime aan op elk element van het Array
say now - INIT now;                          # laat de tijd zien die het script nodig had om te voltooien
Laten we naar bovenstaand voorbeeld kijken:

We doen maar een enkele opdracht: @array.map({ is-prime $_ })
De is-prime subroutine wordt voor elk element van het array achter elkaar aangeroepen:
is-prime @array[0] en dan is-prime @array[1] en dan is-prime @array[2] etc.

Gelukkig kunnen we is-prime tegelijkertijd op meer dan één array element aanroepen:
my @array = 0..50000;                             # vullen van het Array
my @resultaat = @array.race.map({ is-prime $_ }); # roep is-prime aan op elk element van het Array
say now - INIT now;                               # laat de tijd zien die het script nodig had om te voltooien

Merk op dat we race in deze expressie hebben gebruikt. Deze methode maakt het mogelijk om in parallel over de array elementen te gaan.

Nadat je beide voorbeelden (met en zonder race) hebt uitgevoerd, vergelijk dan de tijd die nodig was om de scripts te laten voltooien.

race behoudt de volgorde van de elementen niet. Als je de volgorde wilt behouden, dan moet je hyper gebruiken.

race
my @array = 1..1000;
my @resultaat = @array.race.map( {$_ + 1} );
.say for @resultaat;
hyper
my @array = 1..1000;
my @resultaat = @array.hyper.map( {$_ + 1} );
.say for @resultaat;

Als je beide voorbeelden uitvoert, zul je zien dat de ene wel gesorteerd is en de andere niet.

15.1.2. Taak Parallelisme

my @array1 = (0..49999);
my @array2 = (2..50001);

my @resultaat1 = @array1.map( {is-prime($_ + 1)} );
my @resultaat2 = @array2.map( {is-prime($_ - 1)} );

say @resultaat1 eqv @resultaat2;

say now - INIT now;
Bekijk bovenstaand voorbeeld:
  1. We definiëren 2 arrays

  2. we voeren dezelfde operatie uit op beide arrays en slaan het resultaat op

  3. en kijken of beide resultaten hetzelfde zijn.

Het script wacht totdat @array1.map( {is-prime($_ + 1)} ) klaar is
en gaat dan @array2.map( {is-prime($_ - 1)} ) uitvoeren.

De operaties die we op elk array uitvoeren zijn niet afhankelijk van elkaar.

Dus waarom niet in parallel?
my @array1 = (0..49999);
my @array2 = (2..50001);

my $belofte1 = start @array1.map( {is-prime($_ + 1)} );
my $belofte2 = start @array2.map( {is-prime($_ - 1)} );

my @resultaat1 = await $belofte1;
my @resultaat2 = await $belofte2;

say @resultaat1 eqv @resultaat2;

say now - INIT now;
Uitleg

De start subroutine gaat de gegeven code in parallel uitvoeren maar geeft eerst een Promise (belofte) object terug, of kortweg een belofte.
Als de code kan worden uitgevoerd zonder problemen, dan wordt de belofte gehouden (kept).
Als de code een exception werpt, dan zal de belofte worden gebroken (broken).

De await subroutine wacht op een belofte.
Als de belofte gehouden wordt, dan geeft het de geproduceerde waarden terug.
Als de belofte is gebroken dan zal het de exception (opnieuw) werpen.

Controleer de tijd die nodig was om elk script uit te voeren.

Parallelisme voegt altijd extra overhead toe. Als die overhead relatief groot is, zal een script trager zijn dan de niet parallele versie.
Dat is de reden waarom het gebruik van race, hyper en start in tamelijk simpele scripts een reden van vertraging kan zijn.

15.2. Gelijktijdigheid en Asynchroniciteit

In the previous section, we used parallelism to make a computation faster.
Gelijktijdigheid en Asynchroniciteit zijn antwoorden op verschillende problemen. Een programma moet vaak wachten: op een wekkertje, op een bestand, of op een antwoord van het netwerk.
In plaats van niets te doen tijdens het wachten kan een programma iets anders doen en pas later het resultaat behandelen zodra het beschikbaar is.

15.2.1. Beloften

We hebben al eerder beloften gezien met start en await.
Een belofte staat voor een resultaat dat nog niet beschikbaar is, maar wel op een gegeven moment beschikbaar zal zijn.

Script
my $koffie = start { sleep 2; "Koffie is gezet" };
my $toast  = start { sleep 1; "Toast is klaar" };

say "Ontbijt klaarzetten";
say await $toast;
say await $coffee;
Uitvoer
Ontbijt klaarzetten
Toast is klaar
Koffie is gezet
Uitleg

sleep pauzeert voor het gegeven aantal seconden. We gebruiken het hier om een taak te simuleren die tijd vergt.
start meldt een taak aan en geeft direct een belofte terug. Het wacht niet tot the taak voltooid is. Dat is waarom Ontbijt klaarzetten eerst wordt getoond.
await wacht tot de belofte gehouden wordt, en geeft dan de waarde terug.
Beide taken lopen gelijktijdig, zodat het script maar 2 seconden duurt, in plaats van 3.

15.2.2. Aanvoeren

A promise delivers a single result. A supply delivers many values, one after the other, over a period of time. Een belofte geeft een enkel resultaat terug. Een aanvoer (supply) levert meerdere waarden, één voor één, gedurende een periode.

Supply.interval(1) is een aanvoer die eklek seconde een nieuwe waarde levert: 0, 1, 2 enzovoort.
Om te reageren op elke waarde die wordt geleverd gebruiken we react en whenever.

Script
react {
    whenever Supply.interval(1) -> $seconde {
        say "Tik $seconde";
        done if $seconde == 3;
    }
}
say "Klaar";
Uitvoer
Tik 0
Tik 1
Tik 2
Tik 3
Klaar
Uitleg

whenever voert het bijbehorende blok uit elke keer als een waarde wordt ontvangen.
react zorgt ervoor dat het script blijft wachten totdat done wordt aangeroepen.
done if $seconde == 3 stopped het react blok na de vierde waarde. Zonder die conditie zou het script nooit eindigen.

Een react blok kan meer dan één whenever bevatten. Dit laat het toe dat een script meerdere dingen tegelijk kan doen.

Script
react {
    whenever Supply.interval(1) -> $seconde {
        say "Tick $seconde";
    }
    whenever Promise.in(3.5) {
        say "De tijd is om";
        done;
    }
}
Uitvoer
Tik 0
Tik 1
Tik 2
Tik 3
De tijd is om
Uitleg

Promise.in(3.5) geeft een belofte die gehouden wordt na 3.5 seconden.
De eeste whenever laat elke second een tik zien.
De tweede whenever wacht tot de belofte gehouden worden, en beëindigt dan het react blok.

Zie https://docs.raku.org/language/concurrency voor meer informatie over asynchroon programmeren in Raku

16. Aanroepen van C-bibliotheken

Raku geeft je de mogelijkheid om C-bibliotheken in je programma te gebruiken door middel van de "Native Calling Interface".

NativeCall is a standaard module die altijd met Raku wordt meegeleverd en die je een hoop functionaliteit biedt die het gemakkelijker maakt om programma’s die in C geschreven zijn met programma’s die in Raku geschreven zijn, samen te laten werken.

16.1. Aanroepen van een functie

16.1.1. The C bibliotheek

Bekijk hier het C-programma dat een functie definieert met de naam hallovanc. Deze functie drukt de string Hallo van C af op het scherm. Het ontvangt geen parameters, en het geeft ook geen enkele waarde terug.

ncitest.c
#include <stdio.h>

void hallovanc () {
    printf("Hallo van C\n");
}

16.1.2. Compileren van de bibliotheek

Afhankelijk van het Operating System dat je gebruikt, moet je de volgende commando’s uitvoeren om een bibliotheek te maken van bovenstaand C programma.

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

16.1.3. Het Raku script

Maak vervolgens een bestand aan met het volgende Raku programma, in dezelfde directory waar je de C-bibliotheek hebt gecompileerd. En voer dat programma dan uit.

ncitest.raku
use NativeCall;

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

hallovanc();
Uitleg:

Ten eerste gaven we aan dat we de NativeCall module wilden gebruiken.
Vervolgens maakten we een constante LIBPATH aan waarin de locatie van de C-bibliotheek staat.
Merk op dat $*CWD de huidige directory aangeeft. Vervolgens maakten we een nieuwe Raku subroutine hallovanc die als een verpakking functioneert van de functie met dezelfde naam in de C-bibliotheek die aangegeven wordt door LIBPATH.

Dit alles wordt gedaan door het gebruik van de is native eigenschap.
Tot slot roepen we de Raku subroutine aan.

Uiteindelijk komt het er op neer dat we een subroutine hebben gedefinieerd met de is native eigenschap, en het aangeven van de naam van de C-bibliotheek.

16.2. Andere naam geven aan de functie

Zojuist hebben we gezien dat we een simpele C-functie kunnen aanroepen door deze te verpakken in een Raku subroutine met dezelfde naam door het gebruik van de is native eigenschap.

In sommige gevallen willen we dat name van de Raku subroutine anders is.
Dat kun je doen door het gebruik van de is symbol eigenschap.

Laten we bovenstaand Raku programma aanpassen en de Raku subroutine de naam hallo geven in plaats van hallovanc.

ncitest.raku
use NativeCall;

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

hallo();
Uitleg:

In de situatie waarin de Raku subroutine een andere naam heeft als de naam van de functie aan de C-kant, kun je de is symbol eigenschap gebruiken met de naam van de C-functie.

16.3. Doorgeven van argumenten

Compileer deze aangepaste C-bibliotheek en voer dan het onderstaande Raku programma uit.
Merk op dat we zowel de C-bibliotheek als het Raku programma zo hebben aangepast dat ze beide een string verwachten (char* in C, en Str in Raku).

ncitest.c
#include <stdio.h>

void hallovanc (char* naam) {
    printf("Hallo, %s! Dit is C!\n", naam);
}
ncitest.raku
use NativeCall;

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

hallo('Jane');

16.4. Teruggeven van waarden

Laten we dit nog eens doen zodat het resultaat wordt teruggegeven van een simpele berekening die twee gehele getallen optelt.
Compileer de C-bibliotheek en voer het Raku programma uit.

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

constant LIBPATH = "$*CWD/ncitest";
sub telop(int32,int32 --> int32) is native(LIBPATH) { * }

say telop(2,3);

Merk op dat zowel de C-functie als de Raku subroutine nu 2 gehele getallen verwachten (int in C, en int32 in Raku).

16.5. Types

Je zult je wellicht hebben afgevraagd waarom we int32 hebben gebruikt in plaats van Int in dit laatste voorbeeld.
Sommige Raku typen, zoals Int, Rat, etc., kunnen niet worden gebruikt om waarden naar een C-functie te sturen, of waarden van een C-functie terug te ontvangen.
Men moet in Raku dezelfde typen gebruiken als die in C worden gebruikt.

Gelukkigerwijze bestaan er vele types in Raku die overeenkomen met types zoals die in C worden gebruikt.

C Type Raku Type

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

Arrays: Bijvoorbeeld int* (Array van int) and double* (Array van double)

CArray: Bijvoorbeel CArray[int32] en CArray[num64]

Voor meer informatie over de Native Calling Interface, zie https://docs.raku.org/language/nativecall

17. De Community

  • #raku IRC kanaal. Veel discussies vinden plaats op IRC. Dit zou voor jou het vaste aanspreekpunt moeten zijn voor elke vraag waarop je direct antwoord wilt.

  • StackOverflow Raku-vragen is een plaats waar vragen over Raku meer diepgaand beantwoord worden.

  • Raku Weekly een wekelijks overzicht van veranderingen in en rond Raku

  • Planet Raku is een blog aggregator. Hier kun je blog posts over Raku lezen

  • /r/rakulang ook op Reddit kun je over Raku lezen

  • Raku Foundation de stichting voor de Raku Programming Language