Предназначението на това писание е да ви въведе набързо в езика за програмиране Perl 6. Ако за първи път се сблъсквате с езика, то ще ви даде начален тласък в използуването и изучаването му.

В някои от разделите ще намерите препратки към други, по-пълни и точни части от документацията на Perl 6. Така ще можете да четете направо от източника, в случай че се нуждаете от повече информация по определена тема.

В този урок ще намерите примери по най-обсъжданите теми. За да ги разберете по-добре, отделяйте време да правите всеки от примерите.

Лиценз

Тази работа е лицензирана под "Признание - Споделяне на Споделеното 4.0 Международен Лиценз". За да видите лиценза, посетете

Принос

Ако желаете да допринесете за подобряването на това писание, отидете на

Отзиви

Всякакви отзиви са добре дошли:

Ако тази работа ви харесва, отбележете хранилището със звезда на следния адрес: Github.

Преводи

1. Увод

1.1. Какво е Perl 6

Perl 6 е език от високо ниво, с общо предназначение и постепенна типизация на променливите. Perl 6 е многопарадигмен. Той поддържа процедурно, обектно-ориентирано и функционално програмиране.

Девизи на Perl 6:
  • ИННН Има няколко начина да се направи. TMTOWTDI (Произнася се Tim Toady): There is more than one way to do it.

  • Лесните неща трябва да са лесни, трудните да стават по-лесни, а невъзможните - трудни.

1.2. Понятия

  • Perl 6: Това е спецификация за език за програмиране с набор от тестове. Реализации, които изпълняват тестовете без грешка, могат да се нарекат Perl 6.

  • Rakudo: Е компилатор за Пърл 6.

  • Rakudobrew: Е програма за управление инсталациите на Ракудо.

  • Zef: Е инсталатор на модули за Пърл 6.

  • Rakudo Star: Е вързоп: Ракудо, Зеф, набор от модули за Пърл 6 и документация.

1.3. Инсталиране на Пърл 6

Линукс
  1. Инсталирайте Ракудобрю: https://github.com/tadzik/rakudobrew

  2. Инсталирайте Ракудо: Изпълнете следната команда в терминал rakudobrew build moar

  3. Инсталирайте Зеф: Изпълнете следната команда в терминал rakudobrew build zef

  4. Инсталирайте Task::Star. Това е мета-пакет, съдържащ модулите, които вървят с изданието Rakudo Star. Изпълнете следната команда в терминал zef install Task::Star

За да видите други възможности за инсталация, посетете http://rakudo.org/how-to-get-rakudo/\#Installing-Rakudo-Star-Linux

OSX

Имате четири възможности:

  • Следвайте същите стъпки като в Линукс

  • Инсталирайте с хоумбрю: brew install rakudo-star

  • Инсталирайте с МакПортс: sudo port install rakudo

  • Свалете най-новия инсталатор (файл с разширение .dmg) от http://rakudo.org/downloads/star/

Windows
  1. Свалете най-новия инсталатор (файл с разширение .msi) от http://rakudo.org/downloads/star/ Ако архитектурата на системата ви е 32-битова, свалете файла с x86 в името; ако е 64-битова, свалете файла съдържащ x86_64 в името.

  2. След инсталацията се уверете, че C:\rakudo\bin се намира в системната променлива PATH.

Docker
  1. Вземете официалното изображение за Docker docker pull rakudo-star

  2. След това стартирайте контейнер с изображението docker run -it rakudo-star

1.4. Изпълнение на код, написан на Perl 6

Можете да изпълнявате код на Пърл 6, като използувате директно неговата интерактивна конзола - REPL (Read-Eval-Print Loop). За да направите това, отворете терминал, напишете perl6 в терминала и натиснете [Enter]. Това ще отвори конзолата и в нея ще се появи >. След това напишете някакъв програмен код и натиснете [Enter]. На следващия ред в конзолата ще се появи резултатът от изпълнението на кода. Въведете друг ред, съдържащ програмен код, или въведете exit и натиснете [Enter], за да напуснете конзолата (REPL).

Друг начин за изпълнение е, като въведете програмния код във файл, запишете го и го изпълните. Препоръчва се за разширение на скриптовете, написани на Пърл 6, да се използува .pl6. Изпълнете файла, като напишете в терминал perl6 filename.pl6 и натиснете [Enter]. За разлика от интерактивната конзола (REPL), всеки ред код ще се изпълни последователно, но резултатът не ще се изпише на екрана автоматично. Кодът трябва да съдържа израз, използуващ командата say, за да изведе нещо на стандартния изход (екрана).

Интерактивната конзола се използува най-вече за пробване на специфични парченца код, обикновено едноредови изрази. За програми, състоящи се от повече редове, се препоръчва да се записват във файл и след това да се изпълняват.

Едноредови изрази могат да се изпробват и на командния ред без интерактиванта конзола, като напишете perl6 -e 'your code here' и натиснете [Enter].

Rakudo Star върви с едноредов редактор, който се използува в интерактивната конзола (REPL).

Ако сте инсталирали обикновен Rakudo, вместо Rakudo Star, най-вероятно интерактивната конзола не ви дава възможност да редактирате текущия ред, да ползвате стрелка нагоре и надолу (за да извиквате предишни команди и да ги променяте) или да ползвате табулация (клавишът TAB) за допълване на частично въведени низове. Изпълнете една от следните команда и сте готови.

  • zef install Linenoise ще работи в Windows, Linux и OSX

  • zef install Readline - ако сте на Линукс и предпочитате библиотеката Readline

1.5. Редактори

Тъй като през повечето време ще записваме програмите си във файлове, ни е нужен приличен текстов редактор, който разпознава синтаксиса на Пърл 6.

Аз лично използувам и препоръчвам Atom. Това е модерен редактор и поддържа синтаксиса на Пърл 6. Perl6 FE е допълнителен пакет за оцветяване на кода на Пърл 6 за Атом. Той произхожда от оригиналния пакет, който идва с Атом, но съдържа много подобрения и поправени грешки.

Други членове на общността използуват също Vim, Emacs или Padre.

По-новите версии на Vim идват по подразбиране с поддръжка на синтаксиса на Пърл 6. Emacs и Padre изискват инсталиране на допълнителни пакети.

1.6. Здравей, Свят!

Ще започнем с ритуала hello world.

say 'Здравей, Свят!';

Това може да бъде написано и така:

'Здравей, Свят!'.say;

1.7. Преглед на синтаксиса

Пърл 6 е свободна форма: Свободни сте (през повечето време) да използувате колкото ви е угодно празни пространства (за разлика от Питон - бел. прев.).

Твърденията са обикновено логически ред код. Те завършват с точка и запетая.
say "Здрасти" if True;

Изразите са специален тип твърдение, което връща стойност: 1+2 ще върне 3

Изразите се състоят от Членове и Оператори.

Членове. Те са:

  • Променливи: Съдържат стойност, която може да бъде променяна.

  • Буквални стойности (литерали): Непроменяема, буквална стойност - число или низ.

Оператори. Те са няколко типа:

Тип

Обяснение

Пример

Представка

Преди члена.

++1

Вставка

Между членовете

1+2

Наставка

След члена

1++

Ограждащ

Около члена

(1)

Ограждаща наставка

След един член и ограждащ друг член

Array[1]

1.7.1. Идентификатори (Имена)

Идентификаторите представляват имена, дадени на членовете.

Правила:
  • Трябва да започват с буква или знак за подчертавка.

  • Могат да съдържат числа (ако не са първия знак в името на променливата).

  • Могат да съдържат тирета или апострофи (ако не са първи или последен знак). От дясната страна на тирето или апострофа винаги трябва да има буква.

Правилно

Неправилно

var1

1var

var-one

var-1

var’one

var'1

var1_

var1'

_var

-var

Честo използувани начини за именуване (конвенции):
  • КамилоОбразно: variableNo1

  • шиш-кебап: variable-no1

  • змие_видно: variable_no1

Можете да именувате променливите си както искате, но е добра практика да се спрете на един вариант и да го следвате.

Като използувате смислени имена, ще улесните живота на всички - и вашият, и на вашите колеги.

  • var1 = var2 * var3 е правилно синтактично, но безсмислено.

  • monthly-salary = daily-rate * working-days тези са по-смислени имена за променливи.

1.7.2. Коментари

Коментарът е текст, който се пропуска от компилатора, и се ползва като бележка или пояснение.

Коментарите са три типа:

  • Едноредови:

    # Това е едноредов коментар
  • Вложен/вмъкнат:

    say #`(Това е вмъкнат коментар) "Hello World."
  • Многоредови:

    =begin comment
    Това е многоредов коментар.
    Първа бележка
    Второ пояснение
    =end comment

1.7.3. Кавички

Низовете се ограждат с двойни или единични кавички.

Използувайте двойни кавички, когато:

  • низът ви съдържа апостроф;

  • низът ви съдържа променливи.

say 'Hello World';   # Hello World
say "Hello World";   # Hello World
say "Don't";         # Don't
my $name = 'John Doe';
say 'Hello $name';   # Hello $name
say "Hello $name";   # Hello John Doe

2. Оператори

2.1. Често използувани оператори

Следващата таблица изрежда най-често използуваните оператори.

Оператор Тип Описание Пример Резултат

+

Вставка

Събиране

1 + 2

3

-

Вставка

Изваждане

3 - 1

2

*

Вставка

Умножение

3 * 2

6

**

Вставка

Степенуване

3 ** 2

9

/

Вставка

Деление

3 / 2

1.5

div

Вставка

Деление на цели числа (закръгля надолу)

3 div 2

1

%

Вставка

Деление до остатък

7 % 4

3

%%

Вставка

Делимост

6 %% 4

False

6 %% 3

True

gcd

Вставка

Най-голям общ делител

6 gcd 9

3

lcm

Вставка

Най-малко общо кратно

6 lcm 9

18

==

Вставка

Цифрово равенство

9 == 7

False

!=

Вставка

Цифрово неравенство

9 != 7

True

<

Вставка

По-малко

9 < 7

False

>

Вставка

По-голямо

9 > 7

True

<=

Вставка

По-малко или равно

7 <= 7

True

>=

Вставка

По-голямо или равно

9 >= 7

True

eq

Вставка

Еднаквост между низове

"John" eq "John"

True

ne

Вставка

Низовете не са еднакви

"John" ne "Jane"

True

=

Вставка

Присвояване

my $var = 7

Присвояване на стойността 7 на променливата $var

~

Вставка

Свързване на низове

9 ~ 7

97

"Ей, " ~ "здрасти"

Ей, здрасти

x

Вставка

Повторение на низове

13 x 3

131313

"Здрасти " x 3

Здрасти Здрасти Здрасти

~~

Вставка

Умно съвпадение

2 ~~ 2

True

2 ~~ Int

True

"Perl 6" ~~ "Perl 6"

True

"Perl 6" ~~ Str

True

"enlightenment" ~~ /light/

「light」

++

Представка

Увеличаване

my $var = 2; ++$var;

Увеличава стойността на променливата с 1 и връща резулата 3

Наставка

Увеличаване

my $var = 2; $var++;

Връща стойността на променливата 2 и след това я увеличава с 1

--

Представка

Намаляване

my $var = 2; --$var;

Намалява стойността на променливата с 1 и връща резултата 1

Наставка

Намаляване

my $var = 2; $var--;

Връща стойността на променливата 2 и след това я намалява

+

Представка

Свежда операнда до числова стойност

+"3"

3

+True

1

+False

0

-

Представка

Свежда операнда до числова стойност и връща отрицанието

-"3"

-3

-True

-1

-False

0

?

Представка

Свежда операнда до булева стойност

?0

False

?9.8

True

?"Hello"

True

?""

False

my $var; ?$var;

False

my $var = 7; ?$var;

True

!

Представка

Свежда операнда до булева стойност и връща отрицанието

!4

False

..

Вставка

Създател на поредица

0..5

Създава поредица от 0 до 5

..^

Вставка

Създател на поредица

0..^5

Създава поредица от 0 до 4

^..

Вставка

Създател на поредица

0^..5

Създава поредица от 1 до 5

^..^

Вставка

Създател на поредица

0^..^5

Създава поредица от 1 до 4

^

Представка

Създател на поредица

^5

Също като 0..^5 Създава поредица от 0 до 4

…​

Вставка

Мързелив създател на списък

0…​9999

връща елементите само при поискване

|

Представка

Сплескване

|(0..5)

(0 1 2 3 4 5)

|(0^..^5)

(1 2 3 4)

2.2. Обърнати Оператори

Добавяне на R преди който и да е оператор ще обърне операндите му.

Нормална операция Резултат Обърнат Оператор Резултат

2 / 3

0.666667

2 R/ 3

1.5

2 - 1

1

2 R- 1

-1

2.3. Съкращаващи Оператори

Операторите за съкращения се прилагат върху списък от стойности. Съставят се, като операторът се огради със []

Нормална операция Резултат Съкращаващ оператор Резултат

1 + 2 + 3 + 4 + 5

15

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

15

1 * 2 * 3 * 4 * 5

120

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

120

За да видите пълния списък с оператори и приоритетите им, отидете на https://docs.perl6.org/language/operators

3. Променливи

Променливите в Пърл 6 биват три типа - Скалари, Масиви и Хешове.

Променливите се различават по т.нар сиджил (означава "знак" на латински). Този знак се намира в началото на всяка променлива.

  • $ се използува за скалари

  • @ се използува за масиви

  • % се използува за хешове

3.1. Скалари

Скаларът (Scalar) съдържа единична стойност или указател (reference).

Бел. прев.: Указателите в Пърл не са като указателите в C, и затова са познати повече като референции. Все пак по-подходящата дума е указател, а не референция.

# Низ (String)
my $name = 'Перко Наумов';
say $name;

# Цяло число (Integer)
my $age = 99;
say $age;

В зависимост от стойността, която съдържа скаларната променлива (нейния тип), върху нея могат да се извършват различни действия.

Низ
my $name = 'Перко Наумов';
say $name.uc;
say $name.chars;
say $name.flip;
ПЕРКО НАУМОВ
12
вомуаН окреП
За да видите пълния списък с методите, приложими върху низове (скаларни променливи от тип Str), вижте https://docs.perl6.org/type/Str
Цяло число
my $age = 17;
say $age.is-prime;
True
За да видите пълния списък с методите, приложими върху цели числа (скаларни променливи от тип Int), вижте https://docs.perl6.org/type/Int
Рационално число
my $age = 2.3;
say $age.numerator;
say $age.denominator;
say $age.nude;
23
10
(23 10)
За да видите пълния списък с методите, приложими върху рационални числа (десетични дроби), вижте https://docs.perl6.org/type/Rat

3.2. Масиви

Масивите (Arrays) са списъци, които съдържат множество стойности.

my @animals = 'камила','лама','сова';
say @animals;

От долния пример се вижда, че върху масивите могат да се извършват много операции:

Тилдата ~ се използува за свързване на низове.
Script
my @animals = 'camel','vicuña','llama';
say "В зоологическата градина има " ~ @animals.elems ~ " животни.";
say "Животните са: " ~ @animals;
say "Аз ще осиновя една сова";
@animals.push("owl");
say "Сега в градината ми има: " ~ @animals;
say "The first animal we adopted was the " ~ @animals[0];
@animals.pop;
say "Unfortunately the owl got away and we're left with: " ~ @animals;
say "We're closing the zoo and keeping one animal only";
say "We're going to let go: " ~ @animals.splice(1,2) ~ " and keep the " ~ @animals;
Изход
В зоологическата градина има 3 животни.
Животните са: camel vicuña llama
Аз ще осиновя една сова
Сега в градината ми има: camel vicuña llama owl
The first animal we adopted was the camel
Unfortunately the owl got away and we're left with: camel vicuña llama
We're closing the zoo and keeping one animal only
We're going to let go: vicuña llama and keep the camel
Обяснение

.elems връща броя на елементите в масива.
.push() добавя един или повече елементи към масива.
Можем да достъпим отделен елемент от масива като укажем мястото му @animals[0].
.pop премахва последния елемент от масива и го връща.
.splice(a,b) ще премахне b елемента като започне от позиция a.

3.2.1. Масиви с предопределен размер

Обикновено масив се обявява по следния начин:

my @array;

По подразбиране масивът има неопределен брой елементи, и затова го наричат автоматично-разширяващ се. Масивът ще приеме какъвто и да е брой стойности без ограничение.

Но можем да създаваме и масиви с определен брой елементи. В такива масиви не можете да достъпите или добавяте елементи извън определения размер на масива.

За да обявите масив с определен брой елементи, добавете броя на елементите в квадратни скоби веднага след името на масива.

my @array[3];

Този масив ще може да съдържа най-много три стойности с места от 0 до 2.

my @array[3];
@array[0] = "първа стойност";
@array[1] = "втора стойност";
@array[2] = "трета стойност";

Не можете да добавите четвърта стойност в този масив:

my @array[3];
@array[0] = "първа стойност";
@array[1] = "втора стойност";
@array[2] = "трета стойност";
@array[3] = "четвърта стойност";
Стойност 3 (четвърта стойност) е извън обхвата на масива (стойностите могат да са най-много три - от 0 до 2)

3.2.2. Многомерни масиви

Масивите, които видяхме до тук, са едномерни. За щастие, можем да създаваме и многомерни масиви в Пърл 6.

my @tbl[3;2];

Този масив е двумерен. Първото измерение може да съдържа най-много 3 стойности, а второто - най-много 2.

Представете си го като таблица с 3 реда и 2 колони.

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]
За да видите пълния справочник за масив (Array), посетете https://docs.perl6.org/type/Array

3.3. Хешове

Хешът (Hash) е набор от двойки Ключ/Стойност.
my %столици = ('UK','London','Germany','Berlin');
say %столици;
Ето и друг сбит начин за попълване на хеша:
my %capitals = (UK => 'London', Germany => 'Berlin');
say %capitals;

Някои от методите, които могат да се извикват върху хешове, са:

Скрипт
my %capitals = (UK => 'London', Germany => 'Berlin');
%capitals.push: (France => 'Paris');
say %capitals.kv;
say %capitals.keys;
say %capitals.values;
say "The capital of France is: " ~ %capitals<France>;
Изход
(France Paris Germany Berlin UK London)
(France Germany UK)
(Paris Berlin London)
The capital of France is: Paris
Обяснение

.push: (key => 'Value') добавя нова двойка ключ/стойност.
.kv връща списък, съдържащ всички ключове и стойности.
.keys връща списък, съдържащ всички ключове.
.values връща списък, съдържащ всички стойности.
Можем да достъпим отделна стойност в хеша, като укажем нейния ключ %hash<key>

За да видите пълния справочник за хешовете, посетете https://docs.perl6.org/type/Hash

3.4. Типове

В примерите досега не задавахме типа стойност, който да съдържа променливата.

.WHAT Ще върне типа на стойността, съдържаща се в променливата.
my $var = 'Text';
say $var;
say $var.WHAT;

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

Както виждате от горния пример, типът на стойността в променливата $var първо беше (Str), а след това (Int).

Този начин на програмиране се нарича динамично типизиране. Динамично означава, че променливите могат да съдържат стойности от Всякакъв (Any) тип.

Сега опитайте да изпълните следния пример. Обърнете внимание на използуването на Int пред името на променливата.

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

Присвояването ще се провали и ще върне следната грешка:
Type check failed in assignment to $var; expected Int but got Str
Проверката за тип е неуспешна при присвояване на $var; очакваше се Int, но бе подаден Str

Този път указахме, че типа на променливата ще бъде (Int). Опитът да му присвоим низ (Str) не беше успешен.

Този начин на програмиране се нарича статично типизиране. Статично означава, че типа на променливите се указва предварително и не може да бъде променян.

Пърл 6 е постепенно типизиран; позволява и статично, и динамично типизиране.

Масивите и хешовете също могат да бъдат статично типизирани:
my Int @array = 1,2,3;
say @array;
say @array.WHAT;

my Str @multilingual = "Здравей", "Hello","Salut","Hallo","您好","안녕하세요","こんにちは";
say @multilingual;
say @multilingual.WHAT;

my Str %capitals = (UK => 'London', Germany => 'Berlin');
say %capitals;
say %capitals.WHAT;

my Int %country-codes = (UK => 44, Germany => 49);
say %country-codes;
say %country-codes.WHAT;
Ето списък с най-често използуваните типове:

Най-вероятно никога няма да използувате първите два, но са упоменати, да ги знаете.

Тип

Описание

Пример

Резултат

Mu

Коренът на йерархията на типовете в Перл 6

Any

Подразбиращият се родителски клас за нови класове и за повечето стандартни класове

Cool

Стойност, която може да бъде ползвана като низ и число едновременно

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

13 62

Str

Низ от знакове

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

NOEN

Int

Цяло число (случайна точност)

7 + 7

14

Rat

Рационално число (ограничена точност)

0.1 + 0.2

0.3

Bool

Булева стойност (Истина или Лъжа)

!True

False

3.5. Самонаблюдение

Самонаблюдение (Introspection) е действието по взимане на информация за даден обект, например какъв е типът му.
В един от предишните примери използувахме .WHAT, за да върнем типа на променливата.

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)

Типът на дадена променлива показва каква стойност може да съдържа.
Типът на твърдо обявена празна променлива е типът, с който е била обявена.
Типът на празна променлива, която не е твърдо обявена, е Всякакъв (Any)
За да изчистите стойността на променлива, присвойте ѝ Nil.

3.6. Видимост

Преди да използувате променлива за първи път, трябва да я обявите.

В Пърл 6 се използуват няколко начина за обявяване на променливи. В примерите досега използувахме my.

my $var=1;

Операторът my дава на променливата словна (lexical) видимост. Иначе казано, променливата ще бъде видима (използваема) само в блока от код, в който е обявена.

В Пърл 6 блокът представлява всичко, намиращо се между двойка отваряща и затваряща фигурни скоби - { }. Ако няма определен блок, променливата е достъпна в целия скрипт.

{
  my Str $var = 'Text';
  say $var; # е достъпна
}
say $var; # е недостъпна, връща грешка

Тъй като променливата е видима само в блока, в който е обявена, можете да ползвате същото име за друга променлива в друг блок.

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

3.7. Присвояване и Обвързване

В предишните примери видяхме как да присвояваме стойности на променливи.
Присвояването се прави с помощта на оператора =.

my Int $var = 123;
say $var;

Можем да променим стойността, присвоена на променлива:

Присвояване
my Int $var = 123;
say $var;
$var = 999;
say $var;
Изход
123
999

И напротив - когато обвързваме стойност с променлива, не можем да променим стойността.
Обвързването се извършва с помощта на оператора :=.

Обвързване
my Int $var := 123;
say $var;
$var = 999;
say $var;
Изход
123
Cannot assign to an immutable value
Не може да се присвои към непроменяема стойност
Променливите могат да бъдат обвързвани и с други променливи:
my $a;
my $b;
$b := $a;
$a = 7;
say $b;
$b = 8;
say $a;
Изход
7
8

Обвързването на променливи една с друга е двупосочно.
Резултатът от $a := $b и $b := $a е един и същ.

Повече за променливите ще научите на адрес https://docs.perl6.org/language/variables

4. Функции и мутатори

Важно е да се прави разлика между функции и мутатори.
Функциите не променят състоянието на обектите, върху които са извикани.
Мутаторите (менячи - бел. прев.) променят състоянието на обекта.

Скрипт
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
my @числа = [7,2,4,9,11,3];

@числа.push(99);
say @числа;      #1

say @числа.sort; #2
say @числа;      #3

@числа.=sort;
say @числа;      #4
Изход
[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 е меняч (мутатор); той променя състоянието на масива (#1)

.sort е функция; тя връща подреден масив, но не променя състоянието на масива, върху който се използува:

  • (#2) показва, че връща подреден масив.

  • (#3) показва, че състоянието на масива е все така непроменено.

За да накараме дадена функция да действа като мутатор, използуваме .= вместо . (#4) (ред 9 от скрипта)

5. Цикли и условия

Пърл 6 има много изрази за условия и цикли.

5.1. if

Кодът се изпълнява, само ако условието е изпълнено. Иначе казано - ако изразът се изчисли като Истина (True).

my $age = 19;

if $age > 18 {
  say 'Welcome'
}

В Пърл 6 можем да сменим местата на условието и кода.
Макар местата да са сменени, проверката на условието винаги се изпълнява първа.

my $age = 19;

say 'Добре дошъл' if $age > 18;

В случай че условието не се изпълни, можем да укажем алтернативни блокове код чрез:

  • else

  • elsif

# изпълнение на различен код при различни стойности на променливата
my $брой-места = 9;

if $брой-места <= 5 {
  say 'Аз съм седан'
} elsif $брой-места  <= 7 {
  say 'Аз съм мини-ван'
} else {
  say 'Аз съм ван'
}

5.2. unless

Отрицанието на твърдението, проверявано чрез if, може да бъде изразено чрез unless.

Следният код:

my $чисти-обувки = False;

if not $чисти-обувки {
  say 'Почисти си обувките!'
}

може да бъде написан като:

my $чисти-обувки = False;

unless $чисти-обувки {
  say 'Почисти си обувките!'
}

Отрицание на дадено твърдение се постига чрез ! или not.

unless (условие) се използува вместо if not (условие).

unless не може да има съответстваща else клауза.

5.3. with

with е като if, но проверява дали променливата има присвоена стойност.

my Int $var=1;

with $var {
  say 'Hello'
}

Ако изпълните кода, без да сте присвоили стойност на променливата, нищо няма да се изведе на екрана.

my Int $var;

with $var {
  say 'Hello'
}

without е обратното на with. Същото, каквото е unless за if.

Ако първото with условие не е изпълнено, може да укажете друго условие с orwith.
with и orwith са подобни на if и elsif.

5.4. for

Цикълът for повтаря действието върху множество стойности.

my @array = [1,2,3];

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

Забележете, че създадохме променливата $array-item и приложихме действието *100 върху всеки елемент от масива.

5.5. given

given в Пърл 6 е същото като switch в другите езици, но много по-мощно.

my $var = 42;

given $var {
    when 0..50 { say 'По-малко или равно на 50' }
    when Int { say "е Int" }
    when 42  { say 42 }
    default  { say "к'во?" }
}

При успешно съвпадение, процесът на търсене на удовлетворяване на условието, следващо when, се прекратява.

Ако обаче добавите proceed в блока за изпълнение, процесът на търсене на съвпадение продължава.

my $var = 42;

given $var {
    when 0..50 { say 'По-малко или равно на 50'; proceed }
    when Int { say "е Int"; proceed }
    when 42  { say 42 }
    default  { say "к'во?" }
}

5.6. loop

loop е друг начин за писане на for цикъл.

Всъщност loop е начинът, по който се пишат for циклите в езиците, подобни на C.

Пърл 6 принадлежи към това семейство.

loop (my $i = 0; $i < 5; $i++) {
  say "Текущото число е $i"
}
За да научите повече за циклите и условните изрази, погледнете https://docs.perl6.org/language/control

6. I/O

В Пърл 6 най-често използуваните входно-изходни интерфейси са терминалът и файловете.

6.1. Основни действия в терминал

6.1.1. say

say пише в стандартния изход. Добавя нов ред в края. С други думи, следният код

say 'Hello Mam.';
say 'Hello Sir.';

ще изведе текста в кавичките на два отделни реда.

6.1.2. print

print има подобно поведение като say, но не добавя нов ред.

Заместете say с print и сравнете изхода от действията.

6.1.3. get

get се използува за прихващане на входни данни от терминала.

my $name;

say "Hi, what's your name?";
$name = get;

say "Dear $name welcome to Perl 6";

При изпълнение на горния код, терминалът ще чака да въведете името си. Въведете го и натиснете [Enter]. След това ще видите поздрава.

6.1.4. prompt

prompt е съчетание от print и get.

Горният пример може да бъде написан така:

my $name = prompt "Hi, what's your name? ";

say "Dear $name welcome to Perl 6";

6.2. Изпълнение на външни команди

Две подпрограми могат да се използуват за извикване на външни команди:

  • run Изпълнява външна команда (програма) без посредничеството на системната обвивка.

  • shell Изпълнява команда през системната обвивка. Тя е зависима от операционната система и от обвивката ѝ. Всички мета-знаци на обвивката се интерпретират от нея - включително |, пренасочването на променливите на обкръжението и т.н.

Изпълнете следното, ако сте в Linux/OSX
my $name = 'Neo';
run 'echo', "hello $name";
shell "ls";
Изпълнете следното в Windows
shell "dir";

echo и ls са познати команди от обвивката в Linux:
echo отпечатва текст в терминала (същото като print в Perl 6)
ls показва списък от файлове и папки в текущата папка

dir е същото като ls, но в Windows.

6.3. Писане в и четене от файлове

6.3.1. slurp

slurp се използува за четене на данни от файл наведнъж.

Създайте файл със следното съдържание:

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

6.3.2. spurt

spurt се използува за запис на данни във файл наведнъж.

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

spurt "newdatafile.txt", $newdata;

С изпълнението на горния код ще се създаде нов файл, именуван newdatafile.txt. Той ще съдържа данните от $newdata.

6.4. Работа с файлове и папки

Perl 6 може да покаже списък от папки и файлове без помощта на системни команди (например като използвате ls).

say dir;              # Показва списък със съдържанието на текущата папка
say dir "/Documents"; # Показва списък със съдържанието на указаната папка
-

Освен това, можете да създавате и триете папки.

mkdir "newfolder";
rmdir "newfolder";

mkdir създава нова папка.
rmdir изтрива празна папка. Връща грешка, ако не е празна.

Също така можете да проверявате дали указаният път съществува, дали е файл или папка:

В папката, където ще изпълните долния скрипт, създайте празна папка folder123 и празен файл с разширение pl6 script123.pl6

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

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

say "script123.pl6".IO.f;
say "folder123".IO.f;

IO.e проверява дали файлът съществува.
IO.f проверява дали указаният път е файл.
IO.d проверява дали указаният път е папка.

Потребителите на Windows могат да използуват / или \\ за разделител
C:\\rakudo\\bin
C:/rakudo/bin
За повече информация, свързана с входно-изходните операции, вижте https://docs.perl6.org/type/IO

7. Подпрограми

7.1. Създаване

Подпрограмите (наричани също функции) са начин да се събере накуп набор от действия (функционалност) и да се използва по-късно.

За да създадете подпрограма, напишете ключовата дума sub, последвана от името на подпрограмата. След това подпрограмата може да бъде извиквана чрез изписване на името ѝ.
Разгледайте примера:

sub alien-greeting {
  say "Hello earthlings";
}

alien-greeting;

В този пример е показана подпрограма, която не изисква никакви входни данни.

7.2. Списък с параметри

Подпрограмите могат да изискват входни данни. Тези данни се предоставят чрез подаване на параметри. Една подпрограма може да няма никакви или да има няколко параметъра. Броят и типът на параметрите на една подпрограма се наричат сигнатура (от лат. signatura — обозначение, бел. прев.).

Следващата подпрограма приема низ като параметър.
sub say-hello (Str $name) {
    say "Hello " ~ $name ~ "!!!!"
}
say-hello "Paul";
say-hello "Paula";

7.3. Многократност

Може да създадете няколко подпрограми с едно и също име, но различен списък от параметри (многократно). Когато подпрограмата бъде извикана, средата за изпълнение ще реши коя от тях да изпълни, в зависимост от типа и броя на подадените параметри. Този тип подпрограми се създават както обикновено, но като използувате ключовата дума multi вместо sub.

multi greet($name) {
    say "Good morning $name";
}
multi greet($name, $title) {
    say "Good morning $title $name";
}

greet "Johnnie";
greet "Laura","Mrs.";
Оригиналното заглавие на секцията е "Multiple Dispatch". Множествено разпределение или "много-методи" е свойство на някои програмни езици, при което една функция или метод може да бъде динамично избрана за изпълнение в зависимост от типа и броя на подадените ѝ аргументи. От Уикипедия: https://en.wikipedia.org/wiki/Multiple_dispatch (Бел. Прев.)

7.4. Незадължителни и подразбиращи се параметри

Ако сте създали подпрограма, приемаща един параметър и я извикате, без да ѝ подавате нищо, изпълнението ще се провали.

Пърл 6 ни дава възможност да създаваме подпрограми с:

  • Незадължителни параметри

  • Параметри със стойност по подразбиране

Незадължителните параметри се задават, като в края на името на параметъра (променливата) се добави ?.

sub say-hello($name?) {
  with $name { say "Hello " ~ $name }
  else { say "Hello Human" }
}
say-hello;
say-hello("Laura");

Ако потребителят не подаде параметър, той може да приема стойност по подразбиране.
Това се постига, като присвоим стойността, когато създаваме подпрограмата.

sub say-hello($name="Matt") {
  say "Hello " ~ $name;
}
say-hello;
say-hello("Laura");

7.5. Връщане на стойности

Всички подпрограми дотук правят нещо, показват някакъв текст в терминала.

Понякога обаче, може да поискаме подпрограмата да върне някаква стойност, която да използуваме по-късно в приложението.

По подразбиране, резултатът от изпълнението на последния ред в нашата подпрограма е стойността, която тя връща.

Подразбиращо се връщане на стойност
sub squared ($x) {
  $x ** 2;
}
say "7 squared is equal to " ~ squared(7);

За по-голяма яснота, е добра идея изрично да укажем какво връщаме. Това се прави с ключовата дума return.

Изрично връщане на стойност
sub squared ($x) {
  return $x ** 2;
}
say "7 squared is equal to " ~ squared(7);

7.5.1. Ограничаване на връщаните стойности

В един от предишните примери видяхме, че можем да зададем определен тип на приемания параметър. Същото може да бъде направено с връщаната стойност.

За да ограничим типа на връщаната стойност, използуваме отличителя returns или стрелка --> в сигнатурата.

Използуване на отличителя returns
sub squared ($x) returns Int {
  return $x ** 2;
}
say "1.2 на квадрат е " ~ squared(1.2);
Използуване на стрелката
sub squared ($x --> Int) {
  return $x ** 2;
}
say "1.2 squared is equal to " ~ squared(1.2);

Ако не върнем стойност от същия тип, програмата ни ще хвърли грешка.

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

Ограниченията по тип могат да задават не само типа на връщаната стойност, но и това, дали е дефинирана стойността.

В предишните примери указвахме, че типа на връщаната стойност трябва да бъде Int. Можехме и да укажем изрично, дали върнатият Int трябва да бъде определен (defined) (стойността да не е Nil) или не. Това се постига с използуването на следните сигнатури:
-→ Int:D and -→ Int:U

И така, да се използуват тези ограничения е добра практика.
Ето долу новата версия на предния пример, където е използувано :D (defined), за да задължи връщаната стойност от тип Int да бъде определена (а не неопределена – U (undefined)).

sub squared ($x --> Int:D) {
  return $x ** 2;
}
say "1.2 squared is equal to " ~ squared(1.2);
За повече информация относно подпрограмите и функциите, вижте https://docs.perl6.org/language/functions

8. Функционално Програмиране

В тази глава ще разгледаме част от възможностите на езика, които улесняват функционалното програмиране.

8.1. Функциите са граждани първа класа

Функциите/подпрограмите са граждани първа класа:

  • Могат да се подават като параметри

  • Могат да бъдат връщани от други функции

  • Могат да бъдат присвоявани като стойност на променливи

Прекрасен пример е функцията map.
map е функция от високо ниво, тя приема друга функция като параметър.

Скрипт
my @array = <1 2 3 4 5>;
sub squared($x) {
  $x ** 2
}
say map(&squared,@array);
Изход
(1 4 9 16 25)
Обяснение

Създадохме подпрограма, наречена squared, която повдига на квадрат всяко подадено ѝ число.
След това използувахме map, функция от високо ниво, и подадохме два параметъра - функция и масив.
Изходът е списък от елементите на масива, повдигнати на квадрат.

Забележете, че когато подаваме функция като параметър, трябва да поставим пред името ѝ знака &.

8.2. Безименни функции

Безименната функция се нарича още ламбда.
Безименната функция не е обвързана с идентификатор (тя няма име).

Нека пренапишем примера с map, като използуваме безименна функция.

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

Забележете, че вместо да обявяваме подпрограма и да я подаваме като параметър на map, ние я създадохме направо в безименната подпрограма -> $x {$x ** 2}.

На жаргона на Пърл 6 наричаме това записване остър блок

Остър блок може да се използува също за присвояване на функции на променливи:
my $squared = -> $x {
  $x ** 2
}
say $squared(9);

8.3. Верижност (Chaining)

В Пърл 6 методите могат да бъдат извиквани верижно, така че да не се налага да подавате изхода от един метод на друг.

Всяка вградена функция може да се използува и като метод върху обект. (Бел. прев.)

Ето едно неверижно решение:

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

Първо извикваме функцията unique, като ѝ подаваме @array. След това подаваме изхода от нея на sort и накрая подаваме изхода от подреждането на reverse.

Но можем да се възползваме от възможността за верижно извикване на методи и да запишем примера по следния начин:

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

Вече виждате, че верижното извикване е по-лесно за четене.

8.4. Оператор за подаване

Операторът за подаване, наричан тръба в някои езици за програмиране, представя още по-добре верижното извикване.

Подаване напред
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
@array ==> unique()
       ==> sort()
       ==> reverse()
       ==> my @final-array;
say @final-array;
Обяснение
Тръгваме от `@array`, след което връщаме списък с неповторими елементи
                      после ги подреждаме,
                      обръщаме реда им
                      и накрая съхраняваме изхода във @final-array

Виждате, че последователността на извикване на методите е отгоре надолу.

Подаване назад
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @final-array-v2 <== reverse()
                   <== sort()
                   <== unique()
                   <== @array;
say @final-array-v2;
Обяснение

Подаването назад е като подаването напред, но наобратно.
Последователността на извикване на методите е отдолу нагоре – от последната към първата стъпка.

8.5. Свръх-оператор

Свръх-операторът (hyper operator) >>. ще извика даден метод върху всички елементи от един списък и ще върне списък с резултатите.

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;

Можем да използуваме свръх-оператора и с вградените методи в Пърл 6, например is-prime, който ни казва дали едно число е просто.
Също така, можем да създаваме нови подпрограми и да ги извикваме чрез свръх-оператора. В този случай трябва да поставим & пред името на метода, например &is-even.

Това е много практично, тъй като ни освобождава от писането на цикли for, за да обхождаме всяка стойност от масива.

Пърл 6 гарантира, че редът на изходите от работата на метода, извикан чрез свръх-оператора, ще е същият като на входните стойности.
Но няма гаранция, че Пърл 6 ще извика метода последователно, както е редът на елементите, нито че извикването ще е в същата нишка.
Така че, бъдете внимателни с методи, които имат странични ефекти като say (където страничният ефект е показването на подадената стойност).

8.6. Сливания

Сливането (junction) е логическо съпоставяне на стойности.

В израза долу 1|2|3 е сливане.

my $var = 2;
if $var == 1|2|3 {
  say "Променливата има стойност 1 или 2 или 3"
}

Използуването на сливания предизвиква автоматично създаване на нишки (autothreading); операцията се извършва за всеки елемент от сливането, като всички резултати са събрани в ново сливане и върнати.

8.7. Мързеливи списъци

Мързелив списък е този, който е изчислен мързеливо.
Мързеливо означава отлагане на изчислението на даден израз до момента, когато е необходимо, и избягване повторение на изчислението, като се съхранява резултата в паметта.

Ползите са:

  • Нарастване на производителността чрез избягване на излишни изчисления;

  • Възможността да се създават при необходимост безкрайни структури от данни;

  • Възможността да се управлява изпълнението.

За да построим мързелив списък, използуваме вмъкнатия оператор …​.
Мързеливият списък има начален(лни) елемент(и), генератор (начин за създаване на списъка – бел. прев.) и край.

Примерен мързелив списък
my $lazylist = (1 ... 10);
say $lazylist;

Началният елемент е 1, а крайният е 10. Не е определен генератор за създаване на списъка, така че се използува подразбиращият се генератор – последователно нарастване с единица (+1)
С други думи, този мързелив списък (ако е необходимо) ще върне следните елементи (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Безкраен мързелив списък
my $lazylist = (1 ... Inf);
say $lazylist;

Този списък може да върне (ако е необходимо) всяко цяло число между 1 и безкрайност, т.е. всяко цяло число.

Мързелив списък, построен чрез изведен генератор
my $lazylist = (0,2 ... 10);
say $lazylist;

Първоначалните елементи са 0 и 2, а крайният елемент – 10. Не е определен генератор, но Пърл 6 ще изведе генератора от първоначалните елементи (+2). Този мързелив списък може да върне (ако е необходимо) елементите (0, 2, 4, 6, 8, 10).

Мързелив списък, построен чрез определен генератор.
my $мързел-списък = (0, { $_ + 3 } ... 12);
say $мързел-списък;

В този пример определихме изрично генератор, ограден с { }.
Този списък би върнал (ако е нужно) елементите (0, 3, 6, 9, 12).

Ако използуваме изрично зададен генератор, крайният елемент трябва да е стойност, която е възможно да бъде върната от генератора.
Ако възпроизведем примера горе с краен елемент 10 вместо 12, генераторът няма да спре. Генераторът ще прескочи крайният елемент.

Може да заместите 0 …​ 10 с 0 …​^ * > 10
Чете се по следния начин: От 0 до първата стойност по-голяма от 10 (но без нея).

Това няма да спре генератора
my $lazylist = (0, { $_ + 3 } ... 10);
say $lazylist;
Това ще спре генератора
my $lazylist = (0, { $_ + 3 } ...^ * > 10);
say $lazylist;

8.8. Затваряния

Всички съставени от код обекти в Пърл 6 са затваряния (closures). Това означава, че те могат да се обръщат към лексикални (частни) променливи от заобикалящия ги блок.

sub generate-greeting {
    my $name = "John Doe";
    sub greeting {
      say "Good Morning $name";
    };
    return &greeting;
}
my $generated = generate-greeting;
$generated();

Ако изпълните горния код, той ще изпише Good Morning John Doe в терминала.
Изходът от изпълнението е прост, но интересното в примера е, че вътрешната подпрограма greeting бива върната от външната, преди да е изпълнена.

$generated е затваряне.

Затварянето е специален обект, съчетаващ две неща:

  • Подпрограма;

  • Обкръжението, в което подпрограмата е създадена.

Обкръжението се състои от всички локални променливи, които са били достъпни по време на създаване на подпрограмата. В този случай $generated е затваряне, което включва в себе си подпрограмата greeting и низа John Doe, който съществуваше, когато подпрограмата бе създадена.

Да видим и по-интересен пример.

sub greeting-generator($period) {
  return sub ($name) {
    return "Good $period $name"
  }
}
my $morning = greeting-generator("Morning");
my $evening = greeting-generator("Evening");

say $morning("John");
say $evening("Jane");

В този пример създадохме подпрограма greeting-generator($period), която приема един аргумент $period и връща нова подпрограма. Върнатата подпрограма приема един аргумент $name и връща съставения поздрав.

Всъщност, greeting-generator е "фабрика" за подпрограми. В този пример използвахме greeting-generator, за да създадем две подпрограми. Едната казва Good Morning, а другата – Good Evening.

$morning и $evening са затваряния. Те са създадени по един и същи начин, но съхраняват различно обкръжение.
В обкръжението на $morning $period е Morning. В обкръжението на $evening $period е Evening.

9. Класове и обекти

В предишната глава научихме как Пърл 6 улеснява функционалното програмиране. В тази глава ще разгледаме обектно-ориентираното програмиране в Пърл 6.

9.1. Въведение

Обектно-ориентираното програмиране е една от широко използуваните парадигми напоследък. Обектът е набор от променливи и подпрограми, събрани заедно. Променливите се наричат атрибути (член-променливи), а подпрограмите – методи. Атрибутите определят състоянието, а методите определят поведението на обекта.

Класът е образец (шаблон) за създаване на обекти.

За да разберем взаимовръзката, нека разгледаме следния пример:
(Превеждам и програмния код на български, просто защото е възможно и той ще работи. Не е ли това прекрасно? Пробвайте го в конзолата. Бел. прев.)

В една стая има четирима души.

обекти ⇒ 4 човека

Тези четирима души са Човеци

Клас ⇒ Човек

Те имат различни имена, възраст, пол и народност.

атрибути ⇒ име, възраст, пол, народност

На обектно-ориентиран жаргон казваме, че обектите са инстанции (отделни случаи) на един клас.

Да разгледаме следния скрипт:

class Човек {
  has $.име;
  has $.възраст;
  has $.пол;
  has $.народност;
}

my $иван = Човек.new(име => 'Иван', възраст => 23, пол => 'М', народност => 'българин');
say $иван;

Ключовата дума class се използува за определяне на класа.
Ключовата дума has (има) се използува, за да определи член-променливите на класа.
Методът .new() се нарича конструктор. Той създава обекта като отделен случай на класа, върху който е извикан.

В скрипта горе $иван съдържа указател към нов случай на "Човек", определен чрез Човек.new().
Параметрите, подадени на метода .new(), се използуват за определяне членовете на новосъздадения обект.

На класа може да се даде лексикална видимост, като се използува my:

my class Human {

}

9.2. Капсулиране

Капсулирането (Encapsulation) е понятие в обектно-ориентираното програмиране, което групира набор от данни и методи заедно. Данните (атрибути) трябва да са частни, тоест, достъпни само от вътрешността на обекта. За достъп до данните се използуват методи, наречени аксесори (от access – достъп, бел. прев.).

Двата скрипта долу имат един и същ изход.

Непосредствен достъп до променливата:
my $var = 7;
say $var;
Капсулиране:
my $var = 7;
sub sayvar {
  $var;
}
say sayvar;

Методът sayvar е аксесор. Той опосредства достъпа до променливата, без да имаме пряк достъп до нея.

Капсулирането е улеснено в Пърл 6 чрез използуването на втори знак (twigil). Вторият знак е вторичен сиджил. Той се поставя между първия знак и името на атрибута.
Два вида втори знак се използуват в класовете:

  • ! се използува за изрично указване, че член-променливата е частна.

  • . се използува за автоматично създаване на аксесор на член-променливата.

По подразбиране всички член-променливи са частни, но е добър навик винаги да се ползва ! като втори знак.

Затова, трябва да пренапишем горния клас както следва:

class Human {
  has $!name;
  has $!age;
  has $!sex;
  has $!nationality;
}

my $john = Human.new(name => 'John', age => 23, sex => 'M', nationality => 'American');
say $john;

Добавете в скрипта следния израз: say $john.age;
Ще получим съобщение за грешка: Method 'age' not found for invocant of class 'Human'Методът 'age' не е намерен за извикващия на класа 'Human'
Причината е, че $!age като частна може да бъде ползувана само вътре в обекта. Опитвайки се да я достъпим отвън, получаваме грешка.

Сега заместете has $!age с has $.age и вижте изхода при изпълнение на say $john.age;

9.3. Именувани и позиционни параметри

В Пърл 6 всички класове наследяват готов конструктор .new().
Той може да бъде използуван за създаване на обекти, като му се подават параметри.
На готовия конструктор могат да се подават само именувани параметри.
В предишния пример, ще видите, че всички параметри, подадени на .new(), са именувани.

  • name => 'John'

  • age => 23

Какво, ако не искаме да подаваме името на всеки атрибут, когато създаваме обект?
Тогава трябва да създадем конструктор, който приема позиционни параметри.

class Human {
  has $.name;
  has $.age;
  has $.sex;
  has $.nationality;
  # нов конструктор, който презаписва подразбиращия се.
  method new ($name,$age,$sex,$nationality) {
    self.bless(:$name,:$age,:$sex,:$nationality);
  }
}

my $john = Human.new('John',23,'M','American');
say $john;

9.4. Методи

9.4.1. Въведение

Методите са подпрограмите на обекта.
Както подпрограмите, те са средство за събиране на функционалност на едно място и именуването ѝ. Те приемат параметри, имат сигнатура и могат да бъдат създадени като multi.

Методите се създават с помощта на ключовата дума method.
В общия случай методите се създават, за да извършват някакви действия върху атрибутите на обекта. Това спомага за капсулирането. Атрибутите могат да бъдат променяни само в обекта, чрез използуване на методи. Външният свят може да достъпва само методите и няма достъп до атрибутите.

class Human {
  has $.name;
  has $.age;
  has $.sex;
  has $.nationality;
  has $.eligible;
  method assess-eligibility {
      if self.age < 21 {
        $!eligible = 'No'
      } else {
        $!eligible = 'Yes'
      }
  }

}

my $john = Human.new(name => 'John', age => 23, sex => 'M', nationality => 'American');
$john.assess-eligibility;
say $john.eligible;

След като създадем методите в даден клас, те могат да бъдат извиквани върху обект на този клас чрез използуването на точка:
обект . метод, както е в горния пример: $john.assess-eligibility

Ако искаме да достъпим обекта в тялото на метода, за да извикаме друг метод, ползуваме ключовата дума self.

Ако искаме да достъпим член-променлива в тялото на метод, използуваме втория знак !, дори ако при създаването му сме ползували знака ..
Това е така, защото . създава атрибут с ! и автоматично създава негов аксесор.

В горния пример if self.age < 21 и if $!age < 21 правят едно и също нещо, въпреки че чисто технически са различни:

  • self.age извиква метода (аксесор) .age
    Може да бъде записан и като $.age;

  • $!age представлява непосредствено извикване на член-променливата.

9.4.2. Частни методи

Обикновено методите могат да се извикват от външното обкръжение на класа.

Частните методи могат да се извикват само докато сме вътре в класа.
Възможен случай е метод, който извиква друг метод, за да извърши някакво специфично действие. Методът, който взаимодейства с външния свят, е публичен, докато този, който се извиква вътре в него, трябва да си бъде частен. Не искаме потребителите на нашия клас да го използуват непосредствено и затова го обявяваме като частен.

За да обявим частен метод, използуваме знака ! пред името му.
Частните методи се извикват чрез ! вместо .

method !азсъмчастен {
  # тук си пишем програмния код
}

method азсъмпубличен {
  self!азсъмчастен;
  # правим още нещо
}

9.5. Атрибути на клас

Клас-атрибутите са такива, които принадлежат на класа, а не на обекта, създаден от него.
На тях могат да им се дават стойности при обявяването им.
Клас-атрибутите се обявяват с помощта на my, вместо has.
Те се извикват непосредствено върху класа, вместо върху обектите.

class Human {
  has $.name;
  my $.counter = 0;
  method new($name) {
    Human.counter++;
    self.bless(:$name);
  }
}
my $a = Human.new('a');
my $b = Human.new('b');

say Human.counter;

9.6. Тип на достъпа

Дотук във всички примери използувахме атрибутите само за да вземем информация за обектите.

Как да променим стойността на някой атрибут?
Трябва да го означим като променяем за-писане/за-четене чрез ключовите думи is rw.

class Human {
  has $.name;
  has $.age is rw;
}
my $john = Human.new(name => 'John', age => 21);
say $john.age;

$john.age = 23;
say $john.age;

По подразбиране всички атрибути се обявяват като само за четене, но можете и изрично да зададете is readonly.

9.7. Наследяване

9.7.1. Въведение

Наследяването е друго понятие в обектно-ориентираното програмиране.

Когато създаваме класове, бързо установяваме, че някои атрибути и методи се повтарят в много от тях.
Трябва ли да дублираме код?
Не! Трябва да ползуваме наследяване.

Нека си представим, че искаме да създадем два класа – един за Човек и един за Служител.
Човек има два атрибута: име и възраст.
Служител има четири атрибута: име, възраст, компания и заплата.

Изкушени сте да създадете класовете така:

class Human {
  has $.name;
  has $.age;
}

class Employee {
  has $.name;
  has $.age;
  has $.company;
  has $.salary;
}

Въпреки че технически погледнато това е правилен код, той е беден като замисъл.

По-добър би бил следния вариант:

class Human {
  has $.name;
  has $.age;
}

class Employee is Human {
  has $.company;
  has $.salary;
}

Ключовата дума is (3 л. ед. ч. на глагола "съм" – бел. прев.) определя наследяването.
На обектно-ориентиран жаргон казваме, че Employee е дъщерен клас на Human, и че Human е родителски за Employee.

Всички дъщерни класове наследяват атрибутите и методите на родителския клас, така че няма нужда да ги създаваме наново.

9.7.2. Презаписване

Класовете наследяват всички атрибути и методи от родителските класове. Има случаи обаче, когато искаме някой метод в дъщерния клас да има различно от наследеното поведение. За да постигнем това, ние го създаваме наново в дъщерния клас.
Това се нарича презаписване (overriding).

В примера долу методът introduce-yourself е наследен от класа Employee.

class Human {
  has $.name;
  has $.age;
  method introduce-yourself {
    say 'Hi I am a human being, my name is ' ~ self.name;
  }
}

class Employee is Human {
  has $.company;
  has $.salary;
}

my $john = Human.new(name =>'John', age => 23,);
my $jane = Employee.new(name =>'Jane', age => 25, company => 'Acme', salary => 4000);

$john.introduce-yourself;
$jane.introduce-yourself;

Презаписването се прави така:

class Human {
  has $.name;
  has $.age;
  method introduce-yourself {
    say 'Hi I am a human being, my name is ' ~ self.name;
  }
}

class Employee is Human {
  has $.company;
  has $.salary;
  method introduce-yourself {
    say 'Hi I am a employee, my name is ' ~ self.name ~ ' and I work at: ' ~ self.company;
  }

}

my $john = Human.new(name =>'John',age => 23,);
my $jane = Employee.new(name =>'Jane',age => 25,company => 'Acme',salary => 4000);

$john.introduce-yourself;
$jane.introduce-yourself;

В зависимост от това, от кой клас е създаден обектът, ще бъде изпълнен съответният метод.

9.7.3. Подметоди

Подметодите са такива методи, които не се наследяват от дъщерните класове.
Те са достъпни само в класа, в който са създадени.
Те се създават с помощта на ключовата дума submethod.

9.8. Множествено наследяване

Пърл 6 поддържа множествено наследяване. Един клас може да наследява множество класове.

class bar-chart {
  has Int @.bar-values;
  method plot {
    say @.bar-values;
  }
}

class line-chart {
  has Int @.line-values;
  method plot {
    say @.line-values;
  }
}

class combo-chart is bar-chart is line-chart {
}

my $actual-sales = bar-chart.new(bar-values => [10,9,11,8,7,10]);
my $forecast-sales = line-chart.new(line-values => [9,8,10,7,6,9]);

my $actual-vs-forecast = combo-chart.new(bar-values => [10,9,11,8,7,10],
                                         line-values => [9,8,10,7,6,9]);
say "Actual sales:";
$actual-sales.plot;
say "Forecast sales:";
$forecast-sales.plot;
say "Actual vs Forecast:";
$actual-vs-forecast.plot;
Изход
Actual sales:
[10 9 11 8 7 10]
Forecast sales:
[9 8 10 7 6 9]
Actual vs Forecast:
[10 9 11 8 7 10]
Обяснение

Класът combo-chart ще съдържа два списъка със стойности – един за текущите стойности, изобразени с колонки, и един за прогнозните стойности, изобразени с линия.
Ето защо го създадохме като дъщерен клас на line-chart и bar-chart.
Сигурно забелязахте, че извикването на метода plot върху combo-chart не върна искания резултат. Бе изобразен само един списък.
Защо се случи това?
combo-chart наследява едновременно от line-chart и bar-chart. И двата класа имат метод plot. Когато извикаме този метод върху combo-chart, Пърл 6 ще разреши противоречието, като избере един от наследените методи.

Поправка

За да получим желаното поведение, трябва да презапишем метода plot в combo-chart.

class bar-chart {
  has Int @.bar-values;
  method plot {
    say @.bar-values;
  }
}

class line-chart {
  has Int @.line-values;
  method plot {
    say @.line-values;
  }
}

class combo-chart is bar-chart is line-chart {
  method plot {
    say @.bar-values;
    say @.line-values;
  }
}

my $actual-sales = bar-chart.new(bar-values => [10,9,11,8,7,10]);
my $forecast-sales = line-chart.new(line-values => [9,8,10,7,6,9]);

my $actual-vs-forecast = combo-chart.new(bar-values => [10,9,11,8,7,10],
                                         line-values => [9,8,10,7,6,9]);
say "Actual sales:";
$actual-sales.plot;
say "Forecast sales:";
$forecast-sales.plot;
say "Actual vs Forecast:";
$actual-vs-forecast.plot;
Изход
Actual sales:
[10 9 11 8 7 10]
Forecast sales:
[9 8 10 7 6 9]
Actual vs Forecast:
[10 9 11 8 7 10]
[9 8 10 7 6 9]

9.9. Роли

Ролите са донякъде подобни на класовете, понеже също се състоят от методи и атрибути.

Ролите се обявяват с помощта на ключовата дума role. Класовете, които искат да осъществят (имплементират) една роля, трябва да го направят, като използуват ключовата дума does (3 л. ед. ч. на глагола правя – бел. прев.).

Нека пренапишем примера за множественото наследяване, като използуваме роли:
role bar-chart {
  has Int @.bar-values;
  method plot {
    say @.bar-values;
  }
}

role line-chart {
  has Int @.line-values;
  method plot {
    say @.line-values;
  }
}

class combo-chart does bar-chart does line-chart {
  method plot {
    say @.bar-values;
    say @.line-values;
  }
}

my $actual-sales = bar-chart.new(bar-values => [10,9,11,8,7,10]);
my $forecast-sales = line-chart.new(line-values => [9,8,10,7,6,9]);

my $actual-vs-forecast = combo-chart.new(bar-values => [10,9,11,8,7,10],
                                         line-values => [9,8,10,7,6,9]);
say "Actual sales:";
$actual-sales.plot;
say "Forecast sales:";
$forecast-sales.plot;
say "Actual vs Forecast:";
$actual-vs-forecast.plot;

Пуснете скрипта и ще видите, че изходът е същият като от предишния скрипт.

И сега се питате: Ако ролите се държат като класове, каква полза от тях?
За да си отговорите на този въпрос, променете първия скрипт, в който показахме множественото наследяване. Този, в който забравихме да презапишем метода plot.

role bar-chart {
  has Int @.bar-values;
  method plot {
    say @.bar-values;
  }
}

role line-chart {
  has Int @.line-values;
  method plot {
    say @.line-values;
  }
}

class combo-chart does bar-chart does line-chart {
}

my $actual-sales = bar-chart.new(bar-values => [10,9,11,8,7,10]);
my $forecast-sales = line-chart.new(line-values => [9,8,10,7,6,9]);

my $actual-vs-forecast = combo-chart.new(bar-values => [10,9,11,8,7,10],
                                         line-values => [9,8,10,7,6,9]);
say "Actual sales:";
$actual-sales.plot;
say "Forecast sales:";
$forecast-sales.plot;
say "Actual vs Forecast:";
$actual-vs-forecast.plot;
Изход
===SORRY!===
Method 'plot' must be resolved by class combo-chart because it exists in multiple roles (line-chart, bar-chart)
(Методът плот трябва да бъде "разрешен" в класа combo-chart, защото съществува
в повече от една роля (line-chart, bar-chart))
Обяснение

Ако множество роли са приложени на един и същи клас и се появи противоречие, по време на компилиране ще бъде хвърлена грешка. Това е много по-сигурен подход, в сравнение с множественото наследяване, където такива противоречия не се смятат за грешка и биват разрешавани автоматично по време на изпълнение.

Ролите ви предупреждават, че има противоречие.

9.10. Самонаблюдение

Самонаблюдение (Introspection) е действието, при което вземаме информация за свойствата на един обект. Такива са неговите атрибути, методи или тип.

class Human {
  has Str $.name;
  has Int $.age;
  method introduce-yourself {
    say 'Hi I am a human being, my name is ' ~ self.name;
  }
}

class Employee is Human {
  has Str $.company;
  has Int $.salary;
  method introduce-yourself {
    say 'Hi I am a employee, my name is ' ~ self.name ~ ' and I work at: ' ~ self.company;
  }
}

my $john = Human.new(name =>'John',age => 23,);
my $jane = Employee.new(name =>'Jane',age => 25,company => 'Acme',salary => 4000);

say $john.WHAT;
say $jane.WHAT;
say $john.^attributes;
say $jane.^attributes;
say $john.^methods;
say $jane.^methods;
say $jane.^parents;
if $jane ~~ Human {say 'Jane is a Human'};

Разполагаме със следните средства за самонаблюдение:

  • .WHAT – връща класа, от който е създаден обектът.

  • .^attributes – връща списък с всички атрибути на обекта.

  • .^methods – връща всички методи, които могат да бъдат извикани върху обекта.

  • .^parents – връща всички родителски класове на обекта.

  • ~~ се нарича оператор за умни съвпадения. Той връща Истина (True), ако обектът е създаден от класа, с който е сравняван или го наследява.

За да научите повече за обектно-ориентираното програмиране в Пърл 6, вижте:

10. Обработка на Изключения

10.1. Прихващане на Изключения

Изключенията (Exceptions) представляват специално поведение, което се случва по време на изпълнение, когато нещо се обърка.
Казваме, че програмата ни хвърля изключение.

Да погледнем следния скрипт. Той работи както трябва.

my Str $name;
$name = "Joanna";
say "Hello " ~ $name;
say "How are you doing today?"
Изход
Hello Joanna
How are you doing today?

Сега да видим този скрипт. Той хвърля изключение.

my Str $name;
$name = 123;
say "Hello " ~ $name;
say "How are you doing today?"
Изход
Type check failed in assignment to $name; expected Str but got Int
   in block <unit> at exceptions.pl6:2
(Проверката за типа на $name е неуспешна; очакваше се Str, но се оказа Int в
блок <unit> в exceptions.pl6:2)

Сигурно вече сте забелязали, че при грешка (в този случай присвояване на цяло число на променлива с тип Str) програмата винаги спира и следващите редове не се изпълняват, дори да са правилно написани.

Обработка на изключението е действието, при което прихващаме изключение, което е било хвърлено, за да продължи работата на програмата ни.

my Str $name;
try {
  $name = 123;
  say "Hello " ~ $name;
  CATCH {
    default {
      say "Can you tell us your name again, we couldn't find it in the register.";
    }
  }
}
say "How are you doing today?";
Изход
Can you tell us your name again, we couldn't find it in the register.
How are you doing today?

Обработката на изключението се извършва с помощта на блока try-catch (пробвай-хвани).

try {
  # тук пишете кода
  # ако нещо се обърка, скриптът ще влезе в блока CATCH долу
  # ако всичко е наред, блокът CATCH ще бъде пренебрегнат
  CATCH {
    default {
      # кодът, който се намира тук, ще бъде изпълнен само ако е хвърлено изключение
    }
  }
}

Блокът, в който прихващаме изключението (CATCH), може да бъде обявен по същия начин както given. Това означава, че можем да прихващаме и обработваме по различен начин много типове изключения.

try {
  # тук пишете кода
  # ако нещо се обърка, скриптът ще влезе в блока CATCH долу
  # ако всичко е наред, блокът CATCH ще бъде пренебрегнат
  CATCH {
    when X::AdHoc { # да се направи нещо, в случай че е хвърлено изключение от тип X::AdHoc }
    when X::IO { # да се направи нещо, в случай че е хвърлено изключение от тип X::IO }
    when X::OS { # да се направи нещо, в случай че е хвърлено изключение от тип X::OS }
    default { # да се направи нещо, в случай че е хвърлено изключение от друг тип  }
  }
}

10.2. Хвърляне на Изключения

Пърл 6 ви дава възможност и изрично да хвърляте изключения. Могат да бъдат хвърляни два типа изключения:

  • случайни изключения

  • типови изключения

случайно
my Int $age = 21;
die "Error !";
типово
my Int $age = 21;
X::AdHoc.new(payload => 'Error !').throw;

Случайните изключения се хвърлят, като се използува вградената функция die, последвана от обяснително съобщение за грешката.

Типовите изключения са обекти. Това обяснява и използуването на конструктора .new() в горния пример.
Основният клас на всички типови изключения е X. Ето няколко примера:
X::AdHoc е най-простият тип изключение
X::IO се използува за входно-изходни грешки
X::OS се използува за системни грешки
X::Str::Numeric бива хвърляно при неуспешни опити за превръщане на низове в числа

За да видите пълен списък с типовете изключения и свързаните методи, идете на https://docs.perl6.org/type-exceptions.html

11. Изрази за съвпадения

Изразът за съвпадение или просто съвпадение (regular expression, regex) е последователност от знаци за намиране на съвпадение в текст.
Най-лесният начин за разбирането на тези изрази е да мислите за тях като за шаблони.

Позволявам си да преведа по нов начин наложилото се, но не носещо никакъв смисъл понятие "регулярни изрази" (бел. прев.)
if 'просветление' ~~ m/ свет / {
    say 'Просветление съдържа корена "свет".';
}

В този пример проверяваме с помощта на оператора за умни съвпадения ~~ дали една дума съдържа корена "свет".
В думата "просветление" се търси съвпадение със "свет" m/ свет /

11.1. Обявяване на шаблон

Шаблонът за търсене на съвпадение може да бъде обявен както следва:

  • /свет/

  • m/свет/

  • rx/light/

Празното пространство няма значение, освен ако не е указано нещо друго. m/light/ и m/ light / са едно и също нещо.

11.2. Намиране съвпадения на знаци

Буквено-цифровите знаци и знакът за подчертаване _ се пишат по обичайния начин. Всички други символи трябва да се избягват с обратно наклонена черта или да се ограждат с кавички.

Обратно наклонена черта
if 'Температура: 13' ~~ m/ \: / {
    say "Предоставеният низ съдържа двоеточие : ";
}
Единични кавички
if 'Age = 13' ~~ m/ '=' / {
    say "Низът съдържа знака за равенство = ";
}
Двойни кавички
if 'name@company.com' ~~ m/ "@" / {
    say "Това е валиден адрес за електронна поща, защото съдържа знака @."
}

11.3. Намиране на знаци по категории

Знаците могат да бъдат групирани в категории, а ние можем да търсим съвпадение по тях. Можем също така да търсим по обратното значение на категорията (всичко друго освен нея).

Категория

Израз

Обратно значение

Израз

Знак за дума (буква, цифра или знак за подчертаване)

\w

Всеки друг знак освен знака за дума

\W

Цифра

\d

Всеки знак, който не е цифра

\D

Празно пространство

\s

Всеки знак, който не е празно пространство

\S

Водоравно празно пространство

\h

Всеки знак, който не е водоравно празно пространство

\H

Отвесно празно пространство

\v

Всеки знак, който не е отвесно празно пространство

\V

Табулация

\t

Всеки знак, който не е табулация

\T

Нов ред

\n

Всеки знак, без нов ред

\N

if "Иван123" ~~ / \d / {
  say "Това не е име. Имената не съдържат числа.";
} else {
  say "Това е име."
}
if "Иван-Данов" ~~ / \s / {
  say "Този низ съдържа празно пространство.";
} else {
  say "Този низ не съдържа празно пространство.";
}

11.4. Уникод-свойства

Да се намира съвпадение чрез категории от знаци е удобно. Въпреки това, по-систематичен подход би бил да се използуват уникод-свойства. Уникод-свойствата са оградени с <: >.

if "John123" ~~ / <:N> / {
  say "Contains a number";
} else {
  say "Doesn't contain a number"
}
if "John-Doe" ~~ / <:Lu> / {
  say "Contains an uppercase letter";
} else {
  say "Doesn't contain an upper case letter"
}
if "John-Doe" ~~ / <:Pd> / {
  say "Contains a dash";
} else {
  say "Doesn't contain a dash"
}

11.5. Заместващи знаци

При търсене на съвпадения могат да се ползуват и заместващи знаци.

Точката . дава съвпадение с всякакъв знак.

if 'abc' ~~ m/ a.c / {
    say "Match";
}
if 'a2c' ~~ m/ a.c / {
    say "Match";
}
if 'ac' ~~ m/ a.c / {
    say "Match";
} else {
    say "No Match";
}

11.6. Количествени указатели

Количествените указатели се поставят след знак и указват колко пъти се очаква появата на знака в текста.

Въпросителният знак ? означава нула или един път.

if 'ac' ~~ m/ a?c / {
    say "Match";
} else {
    say "No Match";
}
if 'c' ~~ m/ a?c / {
    say "Match";
} else {
    say "No Match";
}

Звездата * означава нула или повече пъти.

if 'az' ~~ m/ a*z / {
    say "Match";
} else {
    say "No Match";
}
if 'aaz' ~~ m/ a*z / {
    say "Match";
} else {
    say "No Match";
}
if 'aaaaaaaaaaz' ~~ m/ a*z / {
    say "Match";
} else {
    say "No Match";
}
if 'z' ~~ m/ a*z / {
    say "Match";
} else {
    say "No Match";
}

+ означава поне веднъж.

if 'az' ~~ m/ a+z / {
    say "Match";
} else {
    say "No Match";
}
if 'aaz' ~~ m/ a+z / {
    say "Match";
} else {
    say "No Match";
}
if 'aaaaaaaaaaz' ~~ m/ a+z / {
    say "Match";
} else {
    say "No Match";
}
if 'z' ~~ m/ a+z / {
    say "Match";
} else {
    say "No Match";
}

11.7. Резултати от съвпадението

Когато се намери съвпадение на търсенето в някакъв текст, резултатът се съхранява в специалната променлива $/.

Скрипт
if 'Rakudo is a Perl 6 compiler' ~~ m/:s Perl 6/ {
    say "The match is: " ~ $/;
    say "The string before the match is: " ~ $/.prematch;
    say "The string after the match is: " ~ $/.postmatch;
    say "The matching string starts at position: " ~ $/.from;
    say "The matching string ends at position: " ~ $/.to;
}
Изход
The match is: Perl 6
The string before the match is: Rakudo is a
The string after the match is: compiler
The matching string starts at position: 12
The matching string ends at position: 18
Обяснение

$/ връща Обект на Съвпадението (низът, който съответства на търсения шаблон).
Следните методи могат да се извикат върху Обекта на съвпадението:
.prematch връща низа преди съвпадението.
.postmatch връща низа, следващ съвпадението.
.from връща мястото в низа (цяло число), където съвпадението започва.
.to връща мястото в низа (цяло число), където съвпадението свършва.

По подразбиране празното пространство (в шаблон за намиране на съвпадение) няма значение. Ако искаме да намерим съвпадение по шаблон, съдържащ празно пространство, трябва да го укажем изрично. Като поставим :s в шаблона m/:s Perl 6/ указваме празните пространства да се приемат буквално и да не се премахват при компилиране на шаблона. Иначе можехме да запишем израза като m/ Perl\s6 / и да ползуваме \s, което видяхме по-рано като заместител за празно пространство. Ако израз за съвпадение съдържа повече от едно празно пространство, с използуването на :s се оказваме по-ефективни, отколкото ако използуваме \s за всяко празно пространство.

11.8. Пример

Нека проверим дали един адрес за електронна поща е валиден.
За целите на примера ще приемем, че адресът се състои от:
име [точка] фамилия [при] фирма [точка] (com/org/net)

Изразът в този пример за проверка на адреса не е много точен. Единствената му цел е да покаже възможностите в Пърл 6. Не го ползвайте за производствени цели.
Скрипт
my $email = 'john.doe@perl6.org';
my $regex = / <:L>+\.<:L>+\@<:L+:N>+\.<:L>+ /;

if $email ~~ $regex {
  say $/ ~ " is a valid email";
} else {
  say "This is not a valid email";
}
Изход

john.doe@perl6.org is a valid email

Обяснение

<:L> съвпада с отделна буква
<:L>+ съвпада с една или повече букви
\. съвпада с един знак [точка]
\@ съвпада с един знак [при]
<:L+:N> съвпада с низ, състоящ се от една или повече букви и число в края
<:L+:N>+ съвпада с низ, състоящ се от един или повече знаци (букви и числа)

Изразът може да бъде разложен както следва:

  • име <:L>+

  • [точка] \.

  • фамилия <:L>+

  • [при] \@

  • име на фирма <:L+:N>+

  • [точка] \.

  • com/org/net <:L>+

Също така един израз може да бъде разбит на няколко именувани израза
my $email = 'john.doe@perl6.org';
my regex many-letters { <:L>+ };
my regex dot { \. };
my regex at { \@ };
my regex many-letters-numbers { <:L+:N>+ };

if $email ~~ / <many-letters> <dot> <many-letters> <at> <many-letters-numbers> <dot> <many-letters> / {
  say $/ ~ " is a valid email";
} else {
  say "This is not a valid email";
}

Синтаксисът за обявяване на именуван израз за съвпадение е: my regex regex-name { regex definition }
Синтаксисът за извикване на именуван израз за съвпадение е: <regex-name>

За повече информация относно изразите за съвпадение, вижте https://docs.perl6.org/language/regexes

12. Модули в Пърл 6

Пърл 6 е език с общо предназначение. Той е подходящ за всякакви задачи: обработка на текст, графика, уеб, бази данни, мрежови протоколи и т.н.

Многократното използване на код е ключово понятие. Програмистите не трябва да преоткриват колелото с всяка нова задача.

Пърл 6 ни дава възможност да създаваме и разпространяваме модули. Всеки модул представлява пакетиран набор от функционалност, която може да бъде инсталирана и използвана.

Зеф (Zef) е средство за управление на модули, което се разпространява с Rakudo Star.

За да инсталирате отделен модул, напишете следната команда в терминал:

zef install "module name"

Модулите за Пърл 6 се намират на адрес: https://modules.perl6.org/

12.1. Използване на модули

MD5 е хеш-функция от криптографията, която връща 128-битова стойност.
MD5 има много приложения, едно от които е криптиране на пароли, съхранявани в бази от данни. Когато се регистрира нов потребител, неговите данни не се записват като обикновен текст, а се хешират. Причината е, че ако до данните се осъществи неразрешен достъп, паролите няма да бъдат четими.

За наше щастие вече има модул в Пърл 6, който имплементира алгоритъма MD5. Да го инсталираме:
zef install Digest::MD5

Сега пуснете скрипта по-долу:

use Digest::MD5;
my $password = "password123";
my $hashed-password = Digest::MD5.new.md5_hex($password);

say $hashed-password;

За да извикаме функцията md5_hex(), трябва да заредим модула, който я предоставя.
Ключовата дума use зарежда модула в скрипта.

В действителност превръщането на паролата в MD5-сума е недостатъчно, тъй като е податливо на т.нар. речникови атаки. + То трябва да бъде съчетано със "сол" (произволно генерирана стойност – сол към паролата; Бел. ред.) https://en.wikipedia.org/wiki/Salt_(cryptography).

13. Уникод

Уникод е стандарт за кодиране и представяне на текст на почти всички писмени системи в света.
UTF-8 е таблица със знаци, която може да кодира всички възможни знаци от Уникод.

Знаците се състоят от:
Графема: Видимо представяне – как изглежда.
Точка на кода: Число (пореден номер), присвоено на знака.

13.1. Използване на Уникод

Да видим как можем да изобразяваме знаци чрез Уникод.
say "Б";
say "\x0411";
say "\c[CYRILLIC CAPITAL LETTER BE]";

Трите реда горе показват различни начини за изграждане на един знак:

  1. Като просто напишем знака (графема)

  2. Чрез въвеждане на \x, последван от точката на кода (в шестнадесетична бройна система)

  3. Чрез въвеждане на \c, последван от името на точката на кода

Сега да изобразим усмихнато личице
say "☺";
say "\x263a";
say "\c[WHITE SMILING FACE]";
Друг пример, съчетаващ две кодови точки
say "á";
say "\x00e1";
say "\x0061\x0301";
say "\c[LATIN SMALL LETTER A WITH ACUTE]";

Буквата á може да се напише:

  • като използваме нейната единствена кодова точка \x00e1

  • или като съчетание от две кодови точки – a и ляво ударение \x0061\x0301

Ето някои от методите, които могат да се използват върху знака (той е обект):
say "á".NFC;
say "á".NFD;
say "á".uniname;
Изход
NFC:0x<00e1>
NFD:0x<0061 0301>
LATIN SMALL LETTER A WITH ACUTE

NFC връща точката на кода.
NFD разглобява знака и връща кодовата точка на всяка негова част.
uniname връща името на кодовата точка.

Всяка буква от Уникод може да се използва като идентификатор:
my  = 1;
++;
say ;
Уникод знаци могат да се ползват при математически изчисления:
my $var = 2 + ;
say $var;

14. Успоредност, Съгласуваност и Неедновременност

14.1. Успоредност (Parallelism)

В общия случай всички задачи в една програма се изпълняват последователно. Това може да си е съвсем наред, освен ако не отнема много време.

За щастие Пърл 6 позволява да се изпълняват няколко задачи едновременно. Тук е важно да споменем, че успоредност може да означава едно от следните две неща:

  • Успоредност на задачите: Два (или повече) независими израза се изпълняват едновременно.

  • Успоредност на данните: Един израз се изпълнява едновременно върху списък от елементи.

Да започнем с второто.

14.1.1. Успоредност на Данните

my @array = (0..50000);                     # Създаваме елементи в масива
my @result = @array.map({ is-prime $_ });   # Извикваме is-prime върху всеки елемент
say now - INIT now;                         # Показваме времето, което е отнела работата на скрипта
Да разгледаме горния пример:

Извършваме само едно действие @array.map({ is-prime $_ })
Подпрограмата is-prime се извиква последователно върху всеки елемент от масива:
is-prime @array[0], след това is-prime @array[1], после is-prime @array[2] и т. н.

За щастие можем да извикаме is-prime за множество елементи по едно и също време (успоредно):
my @array = (0..50000);                         #Създаваме елементи в масива
my @result = @array.race.map({ is-prime $_ });  #Извикваме is-prime върху всеки елемент
say now - INIT now;                             #Показваме времето, което е отнела работата на скрипта

Обърнете внимание на race в израза. Този метод се грижи за едновременната (успоредна) обработка на елементите от масива.

След като изпълните двата примера ( със и без race), сравнете времената им.

race няма да запази реда на елементите. Ако искате това, ползвайте hyper.

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

Ако изпълните двата примера, ще забележите, че елементите в изхода на втория са подредени, а в първия не са.

14.1.2. Успоредност на задачите

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

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

say @result1 eqv @result2;

say now - INIT now;
Да разгледаме горния пример:
  1. Създадохме два масива;

  2. Приложихме различно действие върху всеки от тях и съхранихме изхода;

  3. Накрая проверихме дали изходите са еднакви.

Скриптът изчаква @array1.map( {is-prime($_ + 1)} ) да завърши
и след това изчислява @array2.map( {is-prime($_ - 1)} )

Двете действия, приложени на всеки от масивите, не зависят едно от друго.

Защо не ги изпълним едновременно?
my @array1 = (0..49999);
my @array2 = (2..50001);

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

my @result1 = await $promise1;
my @result2 = await $promise2;

say @result1 eqv @result2;

say now - INIT now;
Обяснение

Функцията start изпълнява кода и връща обект от тип обещание (promise) или просто обещание.
Ако програмният код се изпълни без грешка, обещанието ще бъде спазено.
Ако кодът хвърли изключение, обещанието ще бъде нарушено.

Функцията await (чакай) чака обещание.
Ако то е спазено, тя ще вземе върнатите стойности.
Ако е нарушено, await ще получи хвърленото изключение.

Проверете времето за изпълнение на всеки скрипт.

Успоредността на изпълнението винаги забавя скрипта, за да създаде нишките, в които се изпълняват задачите. Ако това забавяне не се компенсира при едновременното им изпълнение, скриптът ще изглежда по-бавен. Ето защо, използването на race, hyper, start и await за много прости скриптове, може всъщност да ги забави.

14.2. Съгласуваност и Неедновременност (Concurrency and Asynchrony)

За повече информация относно програмиране при съгласуване на задачите и управление на неедновременни задачи, вижте https://docs.perl6.org/language/concurrency

15. Общността

  • #perl6 IRC канал. Повечето обсъждания се случват в IRC. Там трябва да ходите по всякакви въпроси: https://perl6.org/community/irc

  • p6weekly седмичен преглед на промените в Пърл 6 и новините около него.

  • pl6anet блог агрегатор. Следете статиите, свързани с Пърл 6.

  • /r/perl6 Запишете се в канала, посветен на Пърл 6.