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.
De inhoud is gelicenseerd met de Creative Commons Attribution-ShareAlike 4.0 International License.
Om deze in te zien, ga naar
Als je wilt bijdragen aan dit document, ga dan naar:
Alle commentaar is welkom:
-
naoum@hankache.com - Engels
-
rakuguide@liz.nl - Nederlands
1. Inleiding
1.1. Wat is Raku
Raku is een hogere programmeertaal voor algemeen gebruik met graduele typering. In Raku kan met diverse paradigma’s geprogrammeerd worden. Ondersteund worden onder andere procedureel, object-georiënteerd en functioneel programmeren.
-
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.
-
Rakudo Star: Is een bundel software waarin zich Rakudo, zef, documentatie en een verzameling van Raku modules bevindt.
1.3. Installeren van Raku
Voer de volgende commando’s uit in een Terminal venster om Rakudo Star te installeren:
mkdir ~/rakudo && cd $_
curl -LJO https://rakudo.org/latest/star/src
tar -xzf rakudo-star-*.tar.gz
mv rakudo-star-*/* .
rm -fr rakudo-star-*
./bin/rstar install
echo "export PATH=$(pwd)/bin/:$(pwd)/share/perl6/site/bin:$(pwd)/share/perl6/vendor/bin:$(pwd)/share/perl6/core/bin:\$PATH" >> ~/.bashrc
source ~/.bashrc
Zie https://rakudo.org/star/source voor meer opties.
MacOS kent vier mogelijkheden:
-
Volg dezelfde stappen als voor Linux
-
Installeer met homebrew:
brew install rakudo-star
-
Installeer met MacPorts:
sudo port install rakudo
-
Download de meest recente installer (bestand met .dmg extensie) van https://rakudo.org/latest/star/macos
-
Voor 64-bit systemen: Download de meest recente installer (bestand met .msi extensie) van https://rakudo.org/latest/star/win
-
Zorg ervoor dat
C:\rakudo\bin
in je PATH is na het installeren.
-
Gebruik het officiele Docker image
docker pull rakudo-star
-
Voer daarna een container uit met het image
docker run -it rakudo-star
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 perl6
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 perl6 bestandsnaam.raku
in het terminal venster 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 terminal venster, door perl6 -e 'jouw regel code'
in te typen en dan op [Enter] te drukken.
In de bundel Rakudo Star zit ook een regel-editor die het uitproberen in de REPL nog gemakkelijker maakt. Als je alleen maar Rakudo hebt geïnstalleerd, en niet Rakudo Star, dan heb je standaard niet alle handige regel-editor mogelijkheden (zoals pijltje naar onder/boven om eerder ingetypte regels te bekijken, pijltje links/rechts om je invoer te veranderen, en automatisch invullen met TAB). Voer het volgende commando uit om deze functionaliteit te installeren:
|
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.
Ik gebruik Atom en raadt het gebruik daarvan ook aan. Het is een moderne tekst-editor die standaard uitgeleverd wordt met Raku syntax-markeerder. Perl6-fe is een alternatieve Raku syntax-markeerder voor Atom, afgeleid van het origineel, maar met vele bug-fixes en toevoegingen.
Recente versies van Vim worden standaard uitgeleverd met een syntax-markeerder. Emacs en Padre hebben de installatie van extra bibliotheken nodig.
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;
Expressies zijn een speciaal soort opdracht die resulteren in een waarde:
1+2
geeft 3
terug
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 |
|
Infix |
Tussen twee termen |
|
Postfix |
Volgt na een term |
|
Circumfix |
Staat om een term heen |
|
Postcircumfix |
Achter een term, om een andere term heen |
|
1.7.1. Naamgeving
Je moet termen een naam geven op het moment dat je ze definieert.
-
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 |
|
|
|
|
|
|
|
|
|
|
-
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
2.1. Algemene operatoren
Onderstaande tabel toont de meest voorkomende operatoren.
Operator | Type | Beschrijving | Voorbeeld | Resultaat |
---|---|---|---|---|
|
|
Optelling |
|
|
|
|
Aftrekking |
|
|
|
|
Vermenigvuldiging |
|
|
|
|
Machtsverheffen |
|
|
|
|
Delen |
|
|
|
|
Geheel getal deling (rond af) |
|
|
|
|
Modulo |
|
|
|
|
Deelbaarheid |
|
|
|
|
|||
|
|
Grootse gemene deler |
|
|
|
|
Kleinste gemene veelvoud |
|
|
|
|
Numeriek gelijk |
|
|
|
|
Numeriek niet gelijk |
|
|
|
|
Numeriek kleiner dan |
|
|
|
|
Numeriek groter dan |
|
|
|
|
Numeriek kleiner dan of gelijk aan |
|
|
|
|
Numeriek groter dan of gelijk aan |
|
|
|
|
Numeriek meer/minder/gelijk |
|
|
|
|
|||
|
|
|||
|
|
String gelijk |
|
|
|
|
String niet gelijk |
|
|
|
|
String kleiner dan |
|
|
|
|
String groter dan |
|
|
|
|
String kleiner dan of gelijk |
|
|
|
|
String groter dan of gelijk |
|
|
|
|
String meer/minder/gelijk |
|
|
|
|
|||
|
|
|||
|
|
Slimme meer/minder/gelijk |
|
|
|
|
|||
|
|
Toewijzing |
|
|
|
|
Strings aaneenschakelen |
|
|
|
|
|||
|
|
String herhalen |
|
|
|
|
|||
|
|
Slim vergelijken |
|
|
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
Verhoging |
|
|
|
Verhoging |
|
|
|
|
|
Verlaging |
|
|
|
Verlaging |
|
|
|
|
|
Forceer naar de numerieke waarde |
|
|
|
|
|||
|
|
|||
|
|
Forceer naar de negatieve numerieke waarde |
|
|
|
|
|||
|
|
|||
|
|
Forceer naar de logische waarde |
|
|
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
Forceer naar het tegenovergestelde van de logische waarde |
|
|
|
|
Lijst constructeur |
|
|
|
|
Lijst constructeur |
|
|
|
|
Lijst constructeur |
|
|
|
|
Lijst constructeur |
|
|
|
|
Lijst constructeur |
|
|
|
|
Luie lijst constructeur |
|
|
|
|
Pletten |
|
|
|
|
2.2. 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. Herleidingsoperatoren
Herleidingsoperatoren werken op lijsten.
Je maakt een herleidingsoperator door vierkante haken om de operator te plaatsen []
Normal Operator | Resultaat | Herleidingsoperator | Resultaat |
---|---|---|---|
|
|
|
|
|
|
|
|
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
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.
my $naam = 'Jan Jansen';
say $naam.uc;
say $naam.chars;
say $naam.flip;
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. |
my $leeftijd = 17;
say $leeftijd.is-prime;
True
Bekijk https://docs.raku.org/type/Int voor de complete lijst van methoden die men op een geheel getal (integer) kan uitvoeren. |
my $leeftijd = 2.3;
say $leeftijd.numerator;
say $leeftijd.denominator;
say $leeftijd.nude;
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
.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
.
3.2.1. 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.2. 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
[[1 x] [2 y] [3 z]]
[1 x]
[2 y]
[3 z]
Zie https://docs.raku.org/type/Array voor volledige informatie over arrays. |
3.3. Hashes
my %hoofdsteden = 'VK','Londen','Duitsland','Berlijn';
say %hoofdsteden;
Uitvoer
{Duitsland => Berlijn, VK => Londen}
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
.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>
Zie https://docs.raku.org/type/Hash voor alle informatie over hashes. |
3.4. Types
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.
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;
Je zult hoogstwaarschijnlijk de eerste twee nooit gebruiken, maar we laten ze hier zien om je te laten weten dat ze bestaan.
|
|
|
|
|
|
||
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
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 zal toegankelijk zijn in het gehele Raku script als er geen gebiedsbegrenzing gevonden wordt.
{
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.7. Toewijzing vs. verbinden
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:
my Int $var = 123;
say $var;
$var = 999;
say $var;
Uitvoer
123
999
Daarentegen kunnen we de waarde van een variabele niet veranderen als deze is verbonden met een waarde.
Verbinding wordt gedaan met de :=
operator.
my Int $var := 123;
say $var;
$var = 999;
say $var;
Output
123
Cannot assign to an immutable value
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. Functies en mutators
Het is belangrijk om verschil te maken tussen functies en mutators.
Functies veranderen de toestand van een object waarop ze worden uitgevoerd niet.
Mutators veranderen de toestand van een object wel.
Script
1
2
3
4
5
6
7
8
9
10
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
Output
[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
.push
is een mutator, het verandert de toestand van het array (#1)
.sort
is een functie, het geeft het gesorteerde array terug als een lijst, maar verandert de toestand van het array zelf niet.
-
(#2) laat zien dat een gesorteerde lijst is teruggegeven.
-
(#3) laat zien dat het array zelf onveranderd is.
Men kan een functie als een mutator laten optreden door .=
in plaats van .
te gebruiken (#4) (regel 9 van het script)
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.
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.
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.
Raku hoort bij de familie C-programmeertalen.
loop (my $i = 0; $i < 5; $i++) {
say "Het huidige nummer is $i"
}
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.';
zullen op 2 aparte lijnen worden 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.
my $naam = 'Neo';
run 'echo', "hallo $naam";
shell "ls";
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:
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.4. Werken met bestanden en directories
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
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.
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 makenC:\\rakudo\\bin C:/rakudo/bin |
Zie https://docs.raku.org/type/IO voor meer informatie over invoer en uitvoer. |
7. Subroutines
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. Multiple dispatch
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 "Good morning $naam";
}
multi groet($naam, $titel) {
say "Good morning $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 default waarde
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. 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.
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);
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.5.1. 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.
sub kwadrateer ($x --> Int) {
return $x ** 2;
}
say "1.2 gekwadrateerd is gelijk aan " ~ kwadrateer(1.2);
Als we niet een waarde voor teruggave van het juiste type aangeven, 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 Het is een goede gewoonte om dit soort typebeperkingen te gebruiken.
|
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.
my @array = <1 2 3 4 5>;
sub kwadrateer($x) {
$x ** 2
}
say map(&kwadrateer,@array);
(1 4 9 16 25)
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 dat 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).
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 pointy block.
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 functioneel programmeertalen, geeft een nog beter visualisatie van het aankoppelen van methoden.
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
@array ==> unique()
==> sort()
==> reverse()
==> my @resultaat;
say @resultaat;
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.
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @resultaat <== reverse()
<== sort()
<== unique()
<== @array;
say @resultaat;
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.
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)
my $luielijst = (1 ... Inf);
say $luielijst;
Deze luie lijst kan een getal tussen 1 en oneindig geven, in andere woorden alle natuurlijke getallen.
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)
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. In plaats van Deze generator zal nooit stoppen
Deze generator zal wel stoppen
|
8.8. Closures
Alle code objecten in Raku zijn zogenaamde 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 closure geworden.
Een closure 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.
9.1. Inleiding
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.
Bijkijk 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.
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.
my $var = 7;
say $var;
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. Argumenten op naam vs. argumenten op positie
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 argumenten op naam.
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 argumenten op positie 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. Methoden
9.4.1. Inleiding
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 $.geslacht;
has $.nationaliteit;
has $.geschiktheid;
method bepaal-geschiktheid
if self.leeftijd < 21 {
$!geschiktheid = 'Nee'
} else {
$!geschiktheid = 'Ja'
}
}
}
my $jan = Mens.new(naam => 'Jan', leeftijd => 23, geslacht => 'M', 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 een andere methode 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.4.2. 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.5. 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.6. 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.7. Overerving
9.7.1. Inleiding
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.7.2. 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.name;
}
}
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.name;
}
}
class Werknemer is Mens {
has $.bedrijf;
has $.salaris;
method stel-jezelf-voor {
say 'Hoi, ik ben een werknemer, mijn naam is ' ~ self.name ~ ' 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.7.3. 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.8. 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]
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.
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.9. Rollen
Rollen zijn net als klassen in de zin van dat zij een verzameling van attributen en methoden zijn.
Rollen kunnen worden gedefinieerd met het sleutelwoord role
en klassen die een rol willen "spelen" kunnen dat aangeven met het does
sleutelwoord.
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)
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.10. 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.name;
}
}
class Werknemer is Mens {
has $.bedrijf;
has $.salaris;
method stel-jezelf-voor {
say 'Hoi, ik ben een werknemer, mijn naam is ' ~ self.name ~ ' 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
10.1. Opvangen van Exceptions
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.
Exception handling is het proces van het opvangen van de exceptions die worden geworpen om ervoor te zorgen dat het script blijft werken.
my Str $naam;
try {
$naam = 123;
say "Hallo " ~ $naam;
CATCH {
default {
say "Kun je ons je naam nog eens vertellen? We konden hem niet vinden.";
}
}
}
say "Hoe gaat het?"
Output
Kun je ons je naam nog eens vertellen? We konden hem niet vinden.
Hoe gaat het?
Exception handling wordt gedaan door middel van een try-catch
block.
try {
# 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
CATCH {
default {
# deze code zal alleen uitgevoerd worden als een exception is geworpen
}
}
}
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 {
# 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
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 }
}
}
10.2. 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
my Int $leeftijd = 21;
die "Fout !";
my Int $age = 21;
X::AdHoc.new(payload => 'Fout !').throw;
Ad-hoc exceptions worden geworpen door de die
subroutine gevolgd door het bericht van de exception.
Getypeerde exceptions zijn objecten, daarom moeten we de .new()
constructeur gebruiken in bovenstaand voorbeeld.
Alle getypeerde exceptions erven van de klasse 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. |
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.
if 'Temperatuur: 13' ~~ m/ \: / {
say "De string bevat een dubbele punt :";
}
if 'Leeftijd = 13' ~~ m/ '=' / {
say "De string bevat een is-gelijk teken =";
}
if 'naam@bedrijf.nl' ~~ m/ "@" / {
say "Dit is een juist emailadres 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 "Jan123" ~~ / <:N> / {
say "Bevat een cijfer";
} else {
say "Bevat geen enkel cijfer"
}
if "Jan-Met" ~~ / <: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.
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";
}
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";
}
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. 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.
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;
}
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
$/
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.8. Voorbeeld
Laten we eens kijken of een emailadres goed is of niet.
Voor dit voorbeeld zullen we aannemen dat een werkend emailadres 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 emailadres 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. |
my $email = 'jan.met@raku.org';
my $regex = / <:L>+ \. <:L>+ \@<:L+:N>+ \. <:L>+ /;
if $email ~~ $regex {
say $/ ~ " lijkt een valide emailadres";
} else {
say "Dit emailadres kon niet gevalideerd worden";
}
jan.met@raku.org lijkt een valide emailadres
<: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
De regex kan als volgt uit elkaar worden gehaald:
-
voornaam
<:L>+
-
[punt]
\.
-
achternaam
<:L>+
-
[bij]
\@
-
bedrijfsnaam
<:L+:N>+
-
[punt]
\.
-
top-niveau
<:L>+
my $email = 'jan.met@raku.org';
my regex veel-letters { <:L>+ };
my regex punt { \. };
my regex bij { \@ };
my regex veel-letters-cijfers { <:L+:N>+ };
if $email ~~ / <veel-letters> <punt> <veel-letters> <bij> <veel-letters-nummers> <dot> <veel-letters> / {
say $/ ~ " lijkt een valide emailadres";
} else {
say "Dit emailadres 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. 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 deel uitmaakt van Rakudo Star.
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://modules.raku.org/ |
12.1. Gebruiken van Modules
MD5 is een cryptografische functie die een unieke 128bit waarde (hash) produceert.
MD5 heeft een grote variëteit van applicaties waarvan het opslaan van wachtwoorden in een database er één is.
Zodra een nieuwe gebruiker zich registreert, worden de inloggegevens niet als platte tekst opgeslagen, maar als een hash.
Het idee daarachter is dat in het geval dat een database wordt gestolen, de dief dan niet zal kunnen weten wat de wachtwoorden zijn.
Laat ons een script schrijven dat een MD5 hash van een wachtwoord maakt voordat het in een database wordt opgeslagen.
Gelukkig is er al een Raku module geimplementeerd voor het MD5 algoritme. Laten we het eerst installeren:
zef install Digest::MD5
Voer daarna onderstaand script uit:
use Digest::MD5;
my $wachtwoord = "wachtwoord123";
my $gehashed = Digest::MD5.new.md5_hex($wachtwoord);
say $gehashed;
Om de md5_hex()
functie, die de hashes aanmaakt, aan te kunnen roepen moeten we eerst de benodigde module laden.
Het use
sleutelwoord laadt een module voor gebruik in het script.
In de praktijk is MD5 hashing alleen niet voldoende, omdat het vatbaar is voor zg. "dictionary attacks". Het zou altijd gecombineerd moeten worden met een "salt", zie daarvoor https://en.wikipedia.org/wiki/Salt_(cryptography). |
13. 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.
13.1. Het gebruik van Unicode
say "a";
say "\x0061";
say "\c[LATIN SMALL LETTER A]";
Bovenstaande 3 regels laten verschillende manieren zien om een karakter op te bouwen:
-
Door het karakter (grafeem) direct te schrijven
-
Door
\x
te gebruiken en het codepoint in hexadecimaal aan te geven -
Door
\c
te gebruiken en de naam van het codepoint aan te geven.
say "☺";
say "\x263a";
say "\c[WHITE SMILING FACE]";
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
say "á".NFC;
say "á".NFD;
say "á".uniname;
Output
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.
my $Δ = 1;
$Δ++;
say $Δ;
my $var = 2 + ⅒;
say $var;
13.2. Operaties bekend met Unicode
13.2.1. Numbers
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
13.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)
14. Parallelisme, Gelijktijdigheid en Asynchroniciteit
14.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.
Natuurlijk 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.
14.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
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.
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
hyper
Als je beide voorbeelden uitvoert, zul je zien dat de ene wel gesorteerd is en de andere niet. |
14.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;
-
We definiëren 2 arrays
-
we voeren dezelfde operatie uit op beide arrays en slaan het resultaat op
-
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.
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;
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.
|
14.2. Gelijktijdigheid en Asynchroniciteit
Zie https://docs.raku.org/language/concurrency voor meer informatie over asynchroon programmeren in Raku |
15. 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.
15.1. Aanroepen van een functie
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.
#include <stdio.h>
void hallovanc () {
printf("Hallo van C\n");
}
Afhankelijk van het Operating System dat je gebruikt, moet je de volgende commando’s uitvoeren om een bibliotheek te maken van bovenstaand C programma.
gcc -c -fpic ncitest.c
gcc -shared -o libncitest.so ncitest.o
gcc -c ncitest.c
gcc -shared -o ncitest.dll ncitest.o
gcc -dynamiclib -o libncitest.dylib ncitest.c
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.
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub hallovanc() is native(LIBPATH) { * }
hallovanc();
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.
15.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
.
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub hallo() is native(LIBPATH) is symbol('hallovanc') { * }
hallo();
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.
15.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).
#include <stdio.h>
void hallovanc (char* naam) {
printf("Hallo, %s! Dit is C!\n", naam);
}
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub hallo(Str) is native(LIBPATH) is symbol('hallovanc') { * }
hallo('Jane');
15.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.
int telop (int a, int b) {
return (a + b);
}
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).
15.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 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Arrays: Bijvoorbeeld |
|
Voor meer informatie over de Native Calling Interface, zie https://docs.raku.org/language/nativecall |
16. De Community
-
#raku IRC kanaal. Veel discussies vinden plaats op IRC. Mocht je vragen hebben die relatief snel een antwoord nodig hebben, dan kun je het beste naar https://raku.org/community/irc gaan
-
link:https://stackoverflow.com/questions/tagged/raku is een plaats waar vragen over Raku meer diepgaand beantwoord worden.
-
Rakudo Weekly een wekelijks overzicht van veranderingen in en rond Raku
-
pl6anet is een blog aggregator. Hier kun je blog posts over Raku lezen
-
/r/rakulang ook op Reddit kun je over Raku lezen