- 1. Введение
- 2. Операторы
- 3. Переменные
- 4. Функции и мутаторы
- 5. Циклы и условия
- 6. Ввод-вывод
- 7. Подпрограммы
- 8. Функциональное программирование
- 9. Классы и Объекты
- 10. Обработка исключений
- 11. Регулярные выражения
- 12. Модули Perl 6
- 13. Юникод
- 14. Параллелизм, одновременность и асинхронность
- 15. Интерфейс для нативных вызовов
- 16. Сообщество
Цель этого документа - дать вам краткий обзор языка программирования Perl 6. Тем, кто только знакомится с Perl 6, он должен помочь начать им пользоваться.
Некоторые разделы документа ссылаются на другие (более полные и подробные) части официальной документации Perl 6. Ознакомьтесь с ними, если вам нужно больше информации по определённой теме.
В этом документе вы найдёте примеры кода для решения часто встречающихся задач. Для того, чтобы лучше его понять, выделите время для набора и запуска всех представленных примеров.
Данный документ лицензирован в терминах Creative Commons Attribution-ShareAlike 4.0 International License. Для просмотра копии лицензии, посетите
Если вы хотите внести вклад в этот документ, перейдите по ссылке:
Любые отзывы очень приветствуются:
Если вам понравилось это введение, поставьте "звезду" репозиторию на Github.
-
Болгарский: http://bg.perl6intro.com
-
Китайский: http://zh.perl6intro.com
-
Нидерландский: http://nl.perl6intro.com
-
Французский: http://fr.perl6intro.com
-
Немецкий: http://de.perl6intro.com
-
Индонезийский: http://id.perl6intro.com
-
Итальянский: http://it.perl6intro.com
-
Японский: http://ja.perl6intro.com
-
Португальский: http://pt.perl6intro.com
-
Испанский: http://es.perl6intro.com
-
Турецкий: http://tr.perl6intro.com
-
Русский: http://ru.perl6intro.com
Perl 6 - это универсальный язык высокого уровня с постепенной типизацией. Perl 6 является мультипарадигменным: он поддерживает процедурное, объектно-ориентированное и функциональное программирование.
-
TMTOWTDI (читается как "Тим Тоуди"): "Есть больше одного способа сделать это".
-
Простые вещи должны оставаться простыми, сложные - упрощаться, а невозможное должно быть сложным.
-
Perl 6: Спецификация языка с тестовым набором (test suite). Реализации, которые проходят все тесты спецификации, считаются Perl 6.
-
Rakudo: Компилятор Perl 6.
-
Rakudobrew: Менеджер установки Rakudo.
-
Zef: менеджер модулей Perl 6.
-
Rakudo Star: Сборка, включающий в себя Rakudo, Zef, набор Perl 6 модулей и документацию.
Чтобы установить Rakudo Star, запустите следующие команды в вашем эмуляторе терминала:
wget https://rakudo.perl6.org/downloads/star/rakudo-star-2018.04.tar.gz
tar xfz rakudo-star-2018.04.tar.gz
cd rakudo-star-2018.04
perl Configure.pl --gen-moar --prefix /opt/rakudo-star-2018.04
make install
С другими вариантами можно ознакомиться на http://rakudo.org/how-to-get-rakudo/#Installing-Rakudo-Star-Linux
Есть четыре варианта:
-
Следуйте тем же шагам, что указаны для установки на Linux.
-
Установить с помощью homebrew:
brew install rakudo-star
-
Установить с помощью MacPorts:
sudo port install rakudo
-
Загрузите последний установщик (файл с расширением .dmg) с https://rakudo.org/latest/star/macos
-
Для 64-битной архитектуры: скачайте установщик (файл с расширением .msi) с https://rakudo.org/latest/star/win64
Для 32-битной архитектуры: скачайте установщик (файл с расширением .msi) с https://rakudo.org/latest/star/win32 -
После установки убедитесь, что
C:\rakudo\bin
добавлен в переменную PATH.
-
Установите официальный образ Docker
docker pull rakudo-star
-
Затем запустите контейнер с образом
docker run -it rakudo-star
Запуск кода Perl 6 может быть осуществлён с помощью REPL ("Read-Eval-Print Loop"", цикл «чтение — вычисление — вывод»).
Для этого откройте эмулятор терминала, наберите perl6
в командной строке и
нажмите [Enter]. После этого должно появиться приглашение командной строки - символ >
.
Далее напечатайте строку кода и нажмите [Enter]. REPL выведет
значение строки. Затем вы можете напечатать другую строку или напечатать exit
и нажать [Enter], чтобы покинуть REPL.
Также вы можете написать код в файле, сохранить его и затем запустить.
Рекомендуется сохранять Perl 6 скрипт в файл с расширением .p6
.
Запустите файл, напечатав perl6 имяфайла.p6
в окне эмулятора терминала
и нажав [Enter]. В отличии от использования REPL, результат каждой строки не будет автоматически выводиться: для этого код должен содержать инструкции для вывода, к примеру say
.
REPL в основном используется для проверки определенной части кода, обычно только одной строки. Программы с более чем одной строкой кода рекомендуется сохранять в файлы и только затем запускать их.
Также отдельные строки кода могут быть запущены в командной строке в не-интерактивном режиме
вводом perl6 -e 'ваш код'
и нажатием [Enter].
Tip
|
Rakudo Star также содержит интерактивный редактор, который поможет вам пользоваться REPL в полной мере. Если вы установили только Rakudo вместо Rakudo Star, у вас вероятно не будет определенных возможностей REPL (листание истории клавишами "вверх" и "вниз", "влево" и "вправо" для правки, TAB для автодополнения). Вы можете запустить следующую команду и будете готовы к работе:
|
Поскольку большую часть времени мы будем писать и хранить программы на Perl 6 в файлах, нам нужен хороший текстовый редактор, который будет распознавать синтаксис Perl 6.
Лично я пользуюсь и рекомендую Atom. Это современный текстовый редактор, способный "из коробки" подсвечивать синтаксис Perl 6. Perl 6 FE - альтернативный плагин подсветки кода в Atom, "отколовшийся" от оригинального плагина, содержащий много исправлений и улучшений.
Последние версии Vim’a имеют подсветку синтаксиса "из коробки". Emacs и Padre потребуют установки дополнительных плагинов.
Нам стоит начать с ритуала hello world
.
say 'hello world';
ещё это можно написать так:
'hello world'.say;
Perl 6 это язык со свободной формой написания: в большинстве случаев вы можете использовать сколько угодно пробелов, хоть и в некоторых случаях пробелы имеют значение.
Инструкции обычно являются логическими строками кода, они должны заканчиваться точкой с запятой:
say "Hello" if True;
Выражения являются особым типом инструкций, возвращающим значение:
1+2
вернёт 3
Выражения состоят из Термов и Операторов.
Термами являются:
-
Переменные: значения, которые можно изменять.
-
Литералы: константные значения вроде чисел или строк.
Классификация операторов:
Тип |
Описание |
Пример |
Префиксный |
Перед термом |
|
Инфиксный |
Между термами |
|
Постфиксный |
После терма |
|
Циркумфиксный |
Вокруг терма |
|
Постциркумфиксный |
После одного терма и вокруг другого |
|
Идентификаторы это имена, которые даются термам при определении.
-
Они должны начинаться с алфавитной буквы или с нижнего подчеркивания.
-
Они могут содержать цифры (за исключением первого символа).
-
Они могут содержать тире или апострофы (кроме первого и последнего знака), если есть алфавитная буква с правой стороны от каждого тире или апострофа.
Правильно |
Неправильно |
|
|
|
|
|
|
|
|
|
|
-
Camel case:
variableNo1
-
Kebab case:
variable-no1
-
Snake case:
variable_no1
Вы можете называть свои идентификаторы как пожелаете, но считается хорошей практикой постоянно использовать одно из соглашений во всём коде.
Использование осмысленных имён упростит вашу (и остальных) жизнь программиста.
-
var1 = var2 * var3
синтаксически правильно, но смысл не очевиден. -
monthly-salary = daily-rate * working-days
будет лучшим вариантом названий переменных.
Комментарий - это текст, который игнорируется компилятором и используется для примечаний.
Комментарии делятся на три типа:
-
Однострочный:
# Это однострочный комментарий
-
Встроенный:
say #`(Это встроенный комментарий) "Hello World."
-
Многострочный:
=begin comment Это многострочный комментарий. Комментарий 1 Комментарий 2 =end comment
Строки должны быть ограничены либо двойными, либо одиночными кавычками.
Всегда пользуйтесь двойным кавычками:
-
если строка содержит апостроф
-
если строка содержит интерполируемую переменную.
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
В таблице ниже приведён список наиболее распространённых операторов.
Оператор | Тип | Описание | Пример | Результат |
---|---|---|---|---|
|
|
Сложение |
|
|
|
|
Вычитание |
|
|
|
|
Умножение |
|
|
|
|
Возведение в степень |
|
|
|
|
Деление |
|
|
|
|
Целочисленное деление (округление вниз) |
|
|
|
|
Остаток деления |
|
|
|
|
Проверка делимости |
|
|
|
|
|||
|
|
Наибольший общий делитель |
|
|
|
|
Наименьшее общее кратное |
|
|
|
|
Числовое равенство |
|
|
|
|
Числовое неравенство |
|
|
|
|
Меньше, чем |
|
|
|
|
Больше, чем |
|
|
|
|
Меньше либо равно |
|
|
|
|
Больше либо равно |
|
|
|
|
Трёхстороннее сравнение |
|
|
|
|
|||
|
|
|||
|
|
Равенство строк |
|
|
|
|
Неравенство строк |
|
|
|
|
Строка меньше, чем |
|
|
|
|
Строка больше, чем |
|
|
|
|
Строка меньше либо равна |
|
|
|
|
Строка больше либо равна |
|
|
|
|
Трёхстороннее сравнение строк |
|
|
|
|
|||
|
|
|||
|
|
Умное трёхстороннее сравнение |
|
|
|
|
|||
|
|
Присвоение |
|
|
|
|
Конкатенация строк |
|
|
|
|
|||
|
|
Умножение строк |
|
|
|
|
|||
|
|
Соответствие |
|
|
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
Инкремент |
|
|
|
Инкремент |
|
|
|
|
|
Декремент |
|
|
|
Декремент |
|
|
|
|
|
Приведение операнда к числовому значению |
|
|
|
|
|||
|
|
|||
|
|
Приведение операнда к числовому значению и отрицание |
|
|
|
|
|||
|
|
|||
|
|
Приведение операнда к логическому значению |
|
|
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
Приведение операнда к логическому значению и отрицание |
|
|
|
|
Конструктор диапазона |
|
|
|
|
Конструктор диапазона |
|
|
|
|
Конструктор диапазона |
|
|
|
|
Конструктор диапазона |
|
|
|
|
Конструктор диапазона |
|
|
|
|
Конструктор "ленивого" списка |
|
|
|
|
Выравнивание |
|
|
|
|
Добавление R
перед любым из операторов меняет местами операнды.
Обычная операция | Результат | Обратный оператор | Результат |
---|---|---|---|
|
|
|
|
|
|
|
|
Операторы свёртки работают со списками значений.
Они образуются квадратными скобками []
с обеих сторон оператора.
Обычная операция | Результат | Оператор свёртки | Результат |
---|---|---|---|
|
|
|
|
|
|
|
|
Note
|
С более полным списком операторов и их старшинством вы можете ознакомиться на https://docs.perl6.org/language/operators |
Переменные Perl 6 разделены на три категории: Скаляры, Массивы и Хеши.
Сигил ("печать" на латыни) - это символ, который используется в качестве префикса, категоризирующего переменные.
-
$
используется со скалярами -
@
используется с массивами -
%
используется с хешами
Скаляр содержит одно значение или ссылку.
# Строка
my $name = 'John Doe';
say $name;
# Целое число
my $age = 99;
say $age;
К скаляру может быть применён определённый набор операций, зависящий от значения, которое он содержит.
my $name = 'John Doe';
say $name.uc;
say $name.chars;
say $name.flip;
JOHN DOE
8
eoD nhoJ
Note
|
С полным списком методов, применяемых к строкам, можно ознакомиться на https://docs.perl6.org/type/Str |
my $age = 17;
say $age.is-prime;
True
Note
|
С полным списком методов, применяемых к целым числам, можно ознакомиться на https://docs.perl6.org/type/Int |
my $age = 2.3;
say $age.numerator;
say $age.denominator;
say $age.nude;
23
10
(23 10)
Note
|
С полным списком методов, применяемых к рациональным числам, можно ознакомиться на https://docs.perl6.org/type/Rat |
Массивы являются списками, содержащими множество значений.
my @animals = 'camel','llama','owl';
say @animals;
Как показано на примере ниже, есть множество встроенных операций над массивами:
Tip
|
Тильда ~ используется для конкатенации строк.
|
Скрипт
my @animals = 'camel','vicuña','llama';
say "The zoo contains " ~ @animals.elems ~ " animals";
say "The animals are: " ~ @animals;
say "I will adopt an owl for the zoo";
@animals.push("owl");
say "Now my zoo has: " ~ @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;
Вывод
The zoo contains 3 animals
The animals are: camel vicuña llama
I will adopt an owl for the zoo
Now my zoo has: 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
.
Обычный массив объявляется так:
my @array;
Обычный массив может иметь неограниченную длину, и поэтому он называется автоматически расширяющимся.
В массив можно добавить любое количество значений без ограничений.
Кроме того, мы также можем создавать массивы с фиксированным размером.
К значениям по индексу, превышающему заданный размер, нельзя обращаться.
Чтобы объявить массив с фиксированным размером, обозначьте максимальное количество элементов в квадратных скобках сразу после его имени:
my @array[3];
Этот массив будет хранить не более 3 значений, с индексами от 0 до 2.
my @array[3];
@array[0] = "first value";
@array[1] = "second value";
@array[2] = "third value";
Вы не сможете добавить в этот массив четвёртый элемент:
my @array[3];
@array[0] = "first value";
@array[1] = "second value";
@array[2] = "third value";
@array[3] = "fourth value";
Index 3 for dimension 1 out of range (must be 0..2)
Массивы, которые мы видели до сих пор, были одномерными.
К счастью, мы можем работать с многомерными массивами в Perl 6.
my @tbl[3;2];
Этот массив является двумерным. Первое измерение может содержать максимум три значения, а второе - только две.
Представляйте сетку значений 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]
Note
|
Более подробно с типом Array можно ознакомиться на https://docs.perl6.org/type/Array |
my %capitals = ('UK','London','Germany','Berlin');
say %capitals;
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>
Note
|
Более подробно с типом Hash можно ознакомиться на https://docs.perl6.org/type/Hash |
В прошлых примерах мы не определяли тип значения для переменных.
Tip
|
.WHAT вернёт тип значения переменной.
|
my $var = 'Text';
say $var;
say $var.WHAT;
$var = 123;
say $var;
say $var.WHAT;
Как вы можете видеть на примере выше, тип значения $var
был сначала (Str), а потом стал (Int).
Этот стиль программирования называется динамической типизацией. Динамическая в том плане, что переменные могут содержать значения любого типа.
А сейчас попробуйте запустить следующий пример:
Обратите внимание на Int
перед именем переменной.
my Int $var = 'Text';
say $var;
say $var.WHAT;
Этот код завершится исключением с таким сообщением: Type check failed in assignment to $var; expected Int but got Str
.
Произошло вот что - мы заранее определили, что переменная должна быть типа (Int). Когда же мы попытались присвоить ей (Str), было сгенерировано исключение и выполнение программы прекратилось.
Этот стиль программирования называется статической типизацией. Статическая, потому что типы переменных были заданы до присваивания и их нельзя изменить.
Perl 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;
Вы, скорее всего, никогда не будете использовать первые два и они упоминаются в ознакомительных целях.
|
|
|
|
|
|
||
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Интроспекция - это процесс получения информации о свойствах объекта, например, его типе.
В одном из прошлых примеров мы использовали .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
.
Прежде чем использовать переменную впервые, она должен быть объявлена.
В Perl 6 используют несколько вариантов объявления области видимости. Пока что мы использовали только my
.
my $var=1;
Объявление области видимости my
присваивает переменной лексическую область видимости.
Иными словами, переменная будет доступна только в пределах блока, в котором она была объявлена.
Блок в Perl 6 обозначается { }
.
Переменные, объявленные за пределами блока, будут доступны во всем скрипте.
{
my Str $var = 'Text';
say $var; # достижима
}
say $var; # не достижима, вернёт ошибку
Так как переменная видима только в её блоке, такое же имя переменной может быть использовано в другом блоке.
{
my Str $var = 'Text';
say $var;
}
my Int $var = 123;
say $var;
В предыдущем примере мы видели, как присвоить значение к переменной.
Присвоение совершается используя оператор =
.
my Int $var = 123;
say $var;
Мы можем изменить значение, присвоенное переменной:
my Int $var = 123;
say $var;
$var = 999;
say $var;
Вывод
123
999
С другой стороны, мы не можем изменить значение, которое было привязано к переменной.
Привязка (binding) совершается с использованием оператора :=
.
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
имеют один и тот же результат.
Note
|
Больше о переменных можно узнать на https://docs.perl6.org/language/variables |
Важно различать функции и мутаторы.
Функции не меняют состояние объекта, к которому они применяются.
Мутаторы же вносят изменения в состояние объекта.
Скрипт
my @numbers = [7,2,4,9,11,3];
@numbers.push(99);
say @numbers; #1
say @numbers.sort; #2
say @numbers; #3
@numbers.=sort;
say @numbers; #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 в скрипте)
В Perl 6 есть много способов описать условие и цикл.
Код выполняется только при соблюдённом условии; то есть вычисленное выражение равно True
.
my $age = 19;
if $age > 18 {
say 'Welcome'
}
В Perl 6 вы можем поменять местами код и условие.
Даже если условие определено после кода, оно всегда будет проверяться в первую очередь.
my $age = 19;
say 'Welcome' if $age > 18;
Если условие не соблюдено, можно описать другой блок для выполнения, используя:
-
else
-
elsif
# запускаем тот же самый код с разными значениями переменной
my $number-of-seats = 9;
if $number-of-seats <= 5 {
say 'I am a sedan'
} elsif $number-of-seats <= 7 {
say 'I am 7 seater'
} else {
say 'I am a van'
}
Противоположность оператора if
- unless
.
Данный код:
my $clean-shoes = False;
if not $clean-shoes {
say 'Clean your shoes'
}
может быть записан так:
my $clean-shoes = False;
unless $clean-shoes {
say 'Clean your shoes'
}
Логическое отрицание в Perl 6 осуществляется с помощью !
или not
.
unless (условие)
обычно используется вместо if not (условие)
.
unless
не может иметь ветку else
.
with
действует как и инструкция if
, но проверяет, определена ли переменная.
my Int $var=1;
with $var {
say 'Hello'
}
Если вы запустите код без присвоения значения переменной, ничего не произойдёт.
my Int $var;
with $var {
say 'Hello'
}
without
является противоположностью with
. Эту конструкцию можно сравнить с unless
.
Если первое условие with
не соблюдено, можно описать альтернативную ветка используя orwith
.
with
и orwith
можно сравнить с if
и elsif
.
Цикл for
обходит множество значений.
my @array = [1,2,3];
for @array -> $array-item {
say $array-item * 100
}
Обратите внимание, что мы создали переменную цикла $array-item
, а затем осуществили операцию *100
над каждым из элементов массива.
given
- эквивалент конструкции "switch" в других языках,
но намного круче.
my $var = 42;
given $var {
when 0..50 { say 'Less than or equal to 50'}
when Int { say "is an Int" }
when 42 { say 42 }
default { say "huh?" }
}
После первого успешного сравнения, процесс выбора остановится.
Если необходимо, proceed
позволяет продолжить проверку даже после первого успешного сопоставления.
my $var = 42;
given $var {
when 0..50 { say 'Less than or equal to 50';proceed}
when Int { say "is an Int";proceed}
when 42 { say 42 }
default { say "huh?" }
}
loop
- ещё один способ написать цикл for
.
На самом деле, loop
- это то, как циклы for
записываются в семействе языков программирования C.
Perl 6 принадлежит к этому семейству.
loop (my $i = 0; $i < 5; $i++) {
say "The current number is $i"
}
Note
|
Больше о циклах и условиях можно узнать на https://docs.perl6.org/language/control |
В Perl 6, два наиболее распространённых интерфейса ввода-вывода это Эмулятор терминала и Файлы.
say
делает вывод в стандартный поток вывода. Он добавляет символ перевода строки в конце. Иначе говоря, этот код:
say 'Hello Mam.';
say 'Hello Sir.';
будет выведен как две раздельные строки.
print
, с другой стороны, действует как say
, но не добавляет символ новой строки.
Попробуйте заменить say
на print
и сравните результаты.
get
используется для получения ввода из командной строки.
my $name;
say "Hi, what's your name?";
$name = get;
say "Dear $name welcome to Perl 6";
После запуска кода выше, строка будет ожидать ввода имени. Введите его и нажмите [Enter]. Далее, код поприветствует вас.
Чтобы запустить команду в командной оболочке, могут быть использованы две подпрограммы:
-
run
запускает внешнюю команду без вызова командной оболочки -
shell
запускает команду через системную командную оболочку. Она зависит от платформы и стандартной командной оболочки текущего пользователя. Все метасимволы командной оболочки интерпретируются ею, включая конвейеры, перенаправления, подстановку переменных окружения и так далее.
my $name = 'Neo';
run 'echo', "hello $name";
shell "ls";
shell "dir";
echo
и ls
- распространённые команды оболочки в Linux:
echo
выводит текст в эмулятор терминала (эквивалент print
в Perl 6)
ls
выводит список всех файлов и папок в текущей директории
dir
эквивалент ls
в Windows.
slurp
используется для чтения данных из файла.
Создайте текстовый файл со следующим содержанием:
John 9
Johnnie 7
Jane 8
Joanna 7
my $data = slurp "datafile.txt";
say $data;
В Perl 6 можно получить список содержимого директории, не прибегая к командам оболочки вроде ls
.
say dir; # Перечисляет файлы и папки в текущей директории
say dir "/Documents"; # Перечисляет файлы и папки в указанной директории
Также вы можете создавать и удалять директории.
mkdir "newfolder";
rmdir "newfolder";
mkdir
создаёт новую директорию.
rmdir
удаляет пустую директорию или возвращает ошибку, если она не пуста.
Вы также можете проверить, существует ли путь, является ли он файлом или директорией:
В директории, в которой вы запустите приведённый ниже скрипт, создайте пустую папку folder123
и пустой файл script123.p6
say "script123.p6".IO.e;
say "folder123".IO.e;
say "script123.p6".IO.d;
say "folder123".IO.d;
say "script123.p6".IO.f;
say "folder123".IO.f;
IO.e
проверяет, существует ли директория/файл.
IO.f
проверяет, ведёт ли путь к файлу.
IO.d
проверяет, ведёт ли путь к директории.
ВНИМАНИЕ: Пользователи Windows могут использовать /
или \\
, чтобы разделять директории
C:\\rakudo\\bin
C:/rakudo/bin
Note
|
Больше информации о вводе/выводе можно узнать на https://docs.perl6.org/type/IO |
Подпрограммы (также называемые функциями) это средство объединения и повторного использования функциональности.
Определение подпрограммы начинается с ключевого слова sub
. После определения, она может быть вызвана по имени.
Рассмотрите следующий пример:
sub alien-greeting {
say "Hello earthlings";
}
alien-greeting;
Это пример определения подпрограммы, в которой нет входных данных.
Подпрограмма может зависеть от входных данных. Эти данные передаются посредством аргументов. Подпрограмма может определять ноль либо больше параметров. Количество и тип параметров, которые определены подпрограммой, называются её сигнатурой.
Подпрограмма ниже принимает аргументом строку.
sub say-hello (Str $name) {
say "Hello " ~ $name ~ "!!!!"
}
say-hello "Paul";
say-hello "Paula";
Можно определять несколько подпрограмм с одинаковым именем, но разными сигнатурами.
Когда подпрограмма вызывается, окружение времени выполнения определяет, какой вариант использовать, основываясь на количестве и типах переданных аргументов. Такой вид подпрограмм описывается точно так же, как и обычные подпрограммы, но вместо ключевого слова sub
используется multi
.
multi greet($name) {
say "Good morning $name";
}
multi greet($name, $title) {
say "Good morning $title $name";
}
greet "Johnnie";
greet "Laura","Mrs.";
Если подпрограмма принимает аргумент, но вызвана без этого необходимого аргумента, произойдёт ошибка.
Perl 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");
Все подпрограммы, которые мы пока видели, делали что-то — они выводили некий текст в терминал.
Но иногда мы вызываем подпрограмму для её возвращаемого значения, которое мы можем позднее использовать в потоке выполнения программы.
Если поток выполнения тела функции доходит до конца блока, последняя инструкция или выражение будет использоваться как возвращаемое значение.
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);
В одном из предыдущих примеров мы показали, как мы можем указывать тип определённого аргумента. Для возвращаемых значений тоже можно указывать тип.
Для того, чтобы ограничить возвращаемое значение определённым типом, мы используем либо трейт returns
либо стрелочную нотацию -->
в сигнатуре.
sub squared ($x) returns Int {
return $x ** 2;
}
say "1.2 squared is equal to " ~ 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)
Tip
|
Ограничения по типу могут контролировать не только тип возвращаемого значения, но и то, определено ли оно. В предыдущих примерах, мы указывали, что тип возвращаемого значения должен быть Мы также могли указать, что возвращаемый тип С учётом этого, хорошей практикой является использовать такие ограничения типа. sub squared ($x --> Int:D) {
return $x ** 2;
}
say "1.2 squared is equal to " ~ squared(1.2); |
Note
|
Более подробно о подпрограммах и функциях можно узнать на https://docs.perl6.org/language/functions |
В этом разделе мы взглянем на некоторые из возможностей, которые предоставляет функциональное программирование.
Функции/подпрограммы это объекты первого класса:
-
Они могут быть переданы как аргументы
-
Они могут быть возвращены из других функций
-
Они могут быть присвоены переменным
Наглядным примером является функция 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
, функцию высшего порядка, и передали ей два аргумента: подпрограмма squared
и массив.
Результат - список элементов массива, возведённых в квадрат.
Отметьте, что при передаче подпрограммы аргументом, мы должны добавлять &
в начале её имени.
Анонимные функции также называют лямбдами.
Анонимная функция не привязана к идентификатору (у неё нет имени).
Давайте перепишем пример с map
, используя анонимную функцию
my @array = <1 2 3 4 5>;
say map(-> $x {$x ** 2},@array);
Отметьте, что вместо объявления подпрограммы для получения квадрата и передачи её аргументом к map
, мы определили её внутри анонимной подпрограммы как -> $x {$x ** 2}
.
В жаргоне Perl 6, мы называем такую нотацию заостренный блок (pointy block)
my $squared = -> $x {
$x ** 2
}
say $squared(9);
В Perl 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;
Как можно увидеть, сцепление методов гораздо проще читать.
Оператор ленты, называемый конвейером в некоторых функциональных языках программирования, развивает нотацию сцепления методов.
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;
Обратная лента работает как прямая, но в противоположную сторону.
Порядок вызовов методов снизу-вверх — от последнего до первого шага.
Гипер оператор >>.
вызовет метод для каждого из элементов списка и вернёт список результатов.
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;
Используя гипер оператор мы можем вызывать методы, уже определённые в Perl 6, например, is-prime
, который определяет является ли число простым или нет.
Вдобавок мы можем определять новые подпрограммы и вызывать их, используя этот оператор. В этом случае нам необходимо добавлять &
перед именем метода, например, &is-even
.
Такой подход очень практичен в том смысле, что освобождает от необходимости писать цикл for
для обхода каждого значения.
Warning
|
Perl 6 гарантирует, что порядок результатов будет таким же, как и в оригинальном списке.
Однако, нет гарантии, что Perl 6 на самом деле вызовет методы в оригинальном порядке или в том же самом потоке. Поэтому, будьте осторожны с методами, у которых есть побочные эффекты, например say или print .
|
Скрещение это логическая суперпозиция значений.
В примере ниже, 1|2|3
это скрещение.
my $var = 2;
if $var == 1|2|3 {
say "The variable is 1 or 2 or 3"
}
Использование функций обычно вызывает автоматический трединг; операция производится над каждым элементом скрещения, а все результаты собираются в новое скрещение и возвращаются.
Ленивый список это список, значения которого вычисляются с помощью ленивой стратегии вычисления.
Эта стратегия "замораживает" вычисление выражения до того, когда оно понадобится, и не повторяет вычисления, которые уже были сделаны, сохраняя их в таблице поиска.
Преимущества такого подхода:
-
Увеличение производительности благодаря избеганию ненужных вычислений
-
Возможность создавать потенциально бесконечные структуры данных
-
Возможность определять поток выполнения
Для создания ленивого списка, мы используем инфиксный оператор …
Ленивый список имеет начальные элементы (один либо больше), генератор и предел.
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.
Мы не определили генератор, но используя начальные значения, Perl 6 "выведет", что генератор это (+2)
Такой список вернёт, если необходимо, следующие элементы: (0, 2, 4, 6, 8, 10)
my $lazylist = (0, { $_ + 3 } ... 12);
say $lazylist;
В этом примере, мы явно определили генератор, заключённый в скобки { }
Этот ленивый список может вернуть (если необходимо) следующие элементы: (0, 3, 6, 9, 12)
Warning
|
При использовании явного генератора, предел должен быть одним из значений, которые этот генератор может вернуть. В качестве альтернативы, мы можем заменить Такой генератор не остановится
my $lazylist = (0, { $_ + 3 } ... 10);
say $lazylist; Такой генератор остановится
my $lazylist = (0, { $_ + 3 } ...^ * > 10);
say $lazylist; |
Все объекты кода в Perl 6 являются замыканиями, что означает, что они могут обращаться к лексическим переменным, находящимся в окружающей их области видимости.
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)
, которая принимает новую подпрограмму. Подпрограмма, которую она возвращает, принимает единственный аргумент $name
и возвращает созданное приветствие.
По сути, greeting-generator
это фабрика подпрограмм. В этом примере, мы использовали greeting-generator
, чтобы создать две подпрограммы, одна из которых выводит Good Morning
, а другая - Good Evening
.
$morning
и $evening
это замыкания. Они разделяют одинаковое тело подпрограммы, но разные окружения.
В окружении $morning
, $period
это Morning
. В окружении $evening
, $period
это Evening
.
В предыдущем разделе, мы изучили возможности Perl 6 в функциональном программировании.
В этом разделе мы посмотрим на объектно-ориентированное программирование в Perl 6.
Объектно-ориентированное программирование это одна из широко используемых сегодня парадигм программирования.
Объект - это набор переменных и подпрограмм, объединенный в одну сущность.
Переменные называются атрибутами, а подпрограммы - методами.
Атрибуты определяют состояние, а методы определяют поведение объекта.
Класс это шаблон для создания объектов.
Для того, чтобы понять это отношение, рассмотрим следующий пример:
Есть 4 человека в комнате |
объекты ⇒ 4 человека |
Эти 4 человека - люди |
класс ⇒ человек |
У них разные имена, возраст, пол и национальность |
атрибуты ⇒ имя, возраст, пол, национальность |
В объектно-ориентированном жаргоне, мы называем объекты экземплярами класса.
Рассмотрим следующий скрипт:
class Human {
has $.name;
has $.age;
has $.sex;
has $.nationality;
}
my $john = Human.new(name => 'John', age => 23, sex => 'M', nationality => 'American');
say $john;
Ключевое слово class
используется для определения класса.
Ключевое слово has
используется для определения атрибутов класса.
Метод .new()
вызывает конструктор. Он создаёт объект как экземпляр класса, для которого он был вызван.
В скрипте выше, новая переменная $john
содержит ссылку на новый экземпляр класса "Human", определённую вызовом Human.new()
.
Аргументы переданные в метод .new()
используются для установки значений атрибутов созданного объекта.
Классу можно присвоить лексическую область видимости используя my
:
my class Human {
}
Инкапсуляция это концепция объектно-ориентированного программирования, которая состоит в том, чтобы объединять наборы данных и методов вместе.
Данные (атрибуты) внутри объекта должны быть приватными, иными словами, доступными только лишь изнутри этого объекта.
Для того, чтобы обратиться к атрибутам не в теле метода объекта, мы используем методы, называемые ацессоры(методы доступа).
Два скрипта ниже имеют одинаковый результат.
my $var = 7;
say $var;
my $var = 7;
sub sayvar {
$var;
}
say sayvar;
Метод sayvar
это метод доступа. Он позволяет нам получать доступ к значению переменной без прямого обращения к ней.
Инкапсуляция в Perl 6 упрощается с использованием твигилов.
Твигилы это вторичные сигилы. Они находятся между сигилом и именем атрибута.
В классах используются два твигила:
-
!
используется для явного объявления атрибута как приватного. -
.
используется для автоматической генерации методов доступа к атрибуту.
По умолчанию, все атрибуты приватные, но считается хорошей привычкой всегда использовать твигил !
.
Таким образом, мы можем переписать класс выше как:
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
приватный и может быть использован лишь внутри объекта.
Попытка обратиться к нему снаружи объекта создаст исключение.
Теперь замените has $!age
на has $.age
и проверьте результат инструкции say $john.age;
В Perl 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;
Методы это подпрограммы объекта.
Как и подпрограммы, это средство объединения и повторного использования функциональности, они принимают аргументы, имеют сигнатуру и могут быть определены как 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
это прямое обращение к переменной
Обычные методы могут быть вызваны для объекта за пределами класса.
Приватные методы - это методы, которые могут быть вызваны лишь в пределах объявления класса.
Возможным вариантом использования будет метод, который вызывает другой метод для некого действия. Метод, который взаимодействует с внешним миром, будет публичным, когда тот, к которому обратились, должен оставаться приватным. Мы не хотим, чтобы пользователи вызывали его напрямую, поэтому объявляем его как приватный.
Для объявления приватного метода необходимо использовать твигил !
перед его именем.
Приватные методы вызываются с помощью !
вместо .
method !iamprivate {
# код здесь
}
method iampublic {
self!iamprivate;
# дополнительные действия
}
Атрибуты класса - это атрибуты, которые принадлежат самому классу, а не его объектам.
Они могут быть инициализированы при его объявлении.
Атрибуты класса объявляются с помощью 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;
До этого момента, все примеры, которые мы видели, использовали ацессоры для того, чтобы получить информацию из атрибутов объектов.
Что, если нам нужно модифицировать значение атрибута?
Нам нужно отметить его доступным для чтения/записи, используя ключевое слова 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
Наследование - это ещё одна концепция объектно-ориентированного программирования.
При определении классов, скоро мы можем заметить, что некоторые атрибуты либо методы являются общими для многих классов.
Следует ли нам дублировать код?
НЕТ! Нам следует использовать наследование.
Предположим, что мы хотим объявить два класса: класс Human для людей и класс Employee для рабочих.
Люди имеют 2 атрибута: имя и возраст.
Рабочие имеют 4 атрибута: имя, возраст, компания и зарплата
Предположим, вы собираетесь определить классы так:
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
определяет наследование.
В объектно-ориентированном жаргоне мы говорим, что Employee это потомок Human, а Human это родительский класс Employee.
Все классы-потомки наследуют атрибуты и методы родительского класса, поэтому их не нужно определять вновь.
Классы наследуют все атрибуты и методы их родительских классов.
Однако, существуют случаи, когда мы хотим, чтобы метод потомка работал иначе, чем унаследованный.
Для того, чтобы это осуществить, мы переопределяем метод в классе-потомке.
Это называется переопределением.
В примере ниже, метод 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;
В зависимости от класса объекта, будет вызван нужный метод.
В Perl 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
, Perl 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]
Роли похожи на классы в том смысле, что являются наборами атрибутов и методов.
Роли объявляются с помощью ключевого слова role
. Классы, которые реализовывают роль, делают это с помощью ключевого слова does
.
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)
Если множество ролей применены к одному и тому же классу, и существует конфликт, во время компиляции произойдёт ошибка.
Это гораздо более безопасный подход по сравнению с множественным наследованием, где конфликты не считаются ошибками и просто разрешаются во время выполнения.
Роли предупредят вас, что существует конфликт.
Интроспекция это процесс получения информации об объекте, такой как его тип, атрибуты или методы.
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, если объект создан из класса, с которым проводится сравнение, либо любого из его потомков.
Note
|
Для того, чтобы узнать больше об объектно-ориентированном программировании в Perl 6, посетите: |
Исключения это особое поведение, которое происходит во время выполнения, когда что-то идёт не так.
Говорится, что исключения выбрасываются.
Рассмотрите скрипт ниже, который выполняется корректно:
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.p6:2
Отметьте, что когда происходит ошибка (в данном случае, присвоение числа строковой переменной), программа остановится и следующие строки кода не будут выполнены.
Обработка исключений - это процесс ловли исключений, которые были выброшены, для того, чтобы скрипт продолжал работу.
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 { # сделать что-то, если выброшенное исключение не принадлежит к одному из типов выше }
}
}
В Perl 6 можно выбрасывать исключение явно.
Два типа исключений могут быть выброшены:
-
ad-hoc исключения
-
типизированные исключения
my Int $age = 21;
die "Error !";
my Int $age = 21;
X::AdHoc.new(payload => 'Error !').throw;
Ad-hoc исключения создаются, используя подпрограмму die
, которая принимает сообщение исключения.
Типизированные исключения это объекты, поэтому было необходимо использовать конструктор .new()
в примере выше.
Все типизированные исключения являются потомками класса X
, вот несколько примеров:
X::AdHoc
это простейший тип исключения
X::IO
относится к ошибкам ввода-вывода
X::OS
относится к ошибкам ОС
X::Str::Numeric
относится к попытке привести строку к числу
Note
|
Для полного списка типов исключений и их методов, смотрите https://docs.perl6.org/type-exceptions.html |
Регулярное выражение (англ. regular expressions, жарг. регэкспы или регексы) - это последовательность символов, которые используются для сравнения с образцом.
Регулярные выражения можно воспринимать как шаблон.
if 'enlightenment' ~~ m/ light / {
say "enlightenment contains the word light";
}
В примере выше, оператор умного сравнения ~~
используется, чтобы проверить, содержит ли строка (enlightenment) слово (light).
"Enlightenment" сравнивается с регулярным выражением m/ light /
Регулярное выражение можно определять так:
-
/light/
-
m/light/
-
rx/light/
Если не указано явно, символы пробела игнорируются; m/light/
и m/ light /
означают одно и то же.
Алфавитные символы и цифры, а также нижнее подчёркивание _
означают сами себя.
Все остальные символы должны быть экранированы, используя обратную косую черту или взяты в кавычки.
if 'Temperature: 13' ~~ m/ \: / {
say "The string provided contains a colon :";
}
if 'Age = 13' ~~ m/ '=' / {
say "The string provided contains an equal character = ";
}
if '[email protected]' ~~ m/ "@" / {
say "This is a valid email address because it contains an @ character";
}
Символы могут быть классифицированы по категориям и мы можем сравнивать с ними.
Мы также можем сравнивать с инверсией этой категории (всё, кроме неё):
Категория |
Регэксп |
Инверсия |
Регэксп |
Символ слова (буква, цифра либо нижнее подчёркивание) |
\w |
Любой символ кроме символа слова |
\W |
Цифра |
\d |
Любой символ кроме цифры |
\D |
Пробел |
\s |
Любой символ кроме пробела |
\S |
Горизонтальный пробел |
\h |
Любой символ кроме горизонтального пробела |
\H |
Вертикальный пробел |
\v |
Любой символ кроме вертикального пробела |
\V |
Символ табуляции |
\t |
Любой символ кроме символа табуляции |
\T |
Перевод строки |
\n |
Любой символ кроме перевода строки |
\N |
if "John123" ~~ / \d / {
say "This is not a valid name, numbers are not allowed";
} else {
say "This is a valid name"
}
if "John-Doe" ~~ / \s / {
say "This string contains whitespace";
} else {
say "This string doesn't contain whitespace"
}
Сравнение с категорией символов, как мы видели в предыдущем разделе, очень удобно.
При этом, более систематическим подходом будет использовать свойства (property) Юникода.
Это позволяет нам сравнивать с категорией символов, которые входят и не входят в стандарт ASCII.
Свойства Юникода заключаются в <: >
if "Devanagari Numbers १२३" ~~ / <:N> / {
say "Contains a number";
} else {
say "Doesn't contain a number"
}
if "Привет, Иван." ~~ / <: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"
}
Также в регулярных выражениях можно использовать "символ подстановки" или "wildcard".
Точка .
означает один любой символ.
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";
}
Кванторы записываются после символа и используются для того, чтобы уточнить, как много раз мы ожидаем его встретить.
Знак вопроса ?
означает ноль или один раз.
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";
}
Когда процесс сравнения строки с регулярным выражением успешен,
его результат хранится в специальной переменной $/
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
$/
возвращает объект Match
(строку, которая отвечает регулярному выражению)
Вот часть методов, определённых для объекта Match:
.prematch
возвращает строку, предшествующую совпадению.
.postmatch
возвращает строку, идущую после совпадения.
.from
возвращает начальную позицию совпадения.
.to
возвращает конечную позицию совпадения.
Tip
|
По умолчанию, пробелы в регулярных выражениях игнорируются. Если мы хотим сравнивать с регулярным выражением, явно содержащим символы пробела, мы должны делать это явно. Использование :s в регулярном выражении m/:s Perl 6/ позволяет учитывать символы пробела.Другой вариант - мы могли написать регулярное выражение как m/ Perl\s6 / и использовать \s , который означает пробел.Если регулярное выражение содержит больше одного пробела, использование :s более предпочтительно, чем запись \s для каждого пробела.
|
Давайте проверим, корректный ли электронный адрес или нет.
Для простоты примера мы предположим, что корректный электронный адрес имеет формат:
имя [dot] фамилия [at] компания [dot] (com/org/net)
Warning
|
Регулярное выражение, использованное в этом примере для проверки электронного адреса, не очень точное. Его единственное применение это продемонстрировать регулярные выражения в Perl 6. Не используйте его без изменений в настоящих программах. |
my $email = '[email protected]';
my $regex = / <:L>+\.<:L>+\@<:L+:N>+\.<:L>+ /;
if $email ~~ $regex {
say $/ ~ " is a valid email";
} else {
say "This is not a valid email";
}
[email protected] is a valid email
<:L>
совпадает с одной буквой
<:L>` совпадает с одной или больше буквами +
`\.` совпадает с одним символом [dot] +
`\@` совпадает с одним символом [at] +
`<:L:N>
совпадает с буквой или цифрой
<:L+:N>+
совпадает с одной или более буквой либо цифрой
Это регулярное выражение можно разделить на части так:
-
имя
<:L>+
-
[dot]
\.
-
фамилия
<:L>+
-
[at]
\@
-
компания
<:L+:N>+
-
[dot]
\.
-
com/org/net
<:L>+
my $email = '[email protected]';
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>
Note
|
Больше о регэкспах в Perl 6 можно узнать на https://docs.perl6.org/language/regexes |
Perl 6 это язык программирования общего назначения. Его можно использовать для решения различных задач: работы с текстом, графикой, вебом, базами данных, сетевыми протоколами и так далее.
Возможность повторного использования это очень важная концепция, гласящая, что программисты не должны "изобретать колесо" каждый раз, когда им нужно решить новую задачу.
Perl 6 позволяет создавать и распространять модули. Каждый модуль это отдельная единица функциональности, которая может быть использована повторно после установки.
Zef - это средство для управления модулями, которое поставляется вместе с Rakudo Star.
Чтобы установить конкретный модуль, выполните команду ниже в эмуляторе терминала:
zef install "имя модуля"
Note
|
Список модулей Perl 6 можно найти на https://modules.perl6.org/ |
MD5 это криптографическая хеш-функция, результатом которой является 128-битное значение хеша.
У MD5 есть множество применений в приложениях, включая шифрование паролей, хранящихся в базе данных.
Когда новый пользователь регистрируется, его данные хранятся не в чистом виде, а в хешированом.
Идея заключается в том, что если база данных будет скомпрометирована, атакующий не сможет узнать пароли.
К счастью, нам не нужно реализовывать алгоритм MD5 вручную; существует модуль, написанный на Perl 6, где это реализовано.
Давайте установим его:
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
загружает модуль, чтобы его можно было использовать в скрипте.
Warning
|
На практике, само по себе MD5 хеширование не является достаточным, потому что легко уязвимо к атакам по словарю. Его следует комбинировать с солью https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D0%BB%D1%8C_(%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%8F). |
Юникод это стандарт для кодирования и представления текста для большинства систем письменности в мире.
UTF-8 это символьная кодировка, с помощью которой можно кодировать все возможные символы (или "коды символов") в Юникоде.
Символы в нём определяются через:
Графема: Визуальное отображение.
Код символа: Число, присвоенное символу.
Имя кода символа: Имя присвоенное символу.
say "a";
say "\x0061";
say "\c[LATIN SMALL LETTER A]";
Три строчки выше являются примером разных способов построения символа:
-
Написание символа напрямую (графемы)
-
Использование
\x
и кода символа -
Использование
\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;
Арабские числа состоят из десяти цифр: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Такой числовой набор самый распространённый в мире.
Невзирая на это, разные цифры используются в разных частях мира, хотя и с меньшей распространённостью.
Для того, чтобы использовать системы числительных кроме арабских, никаких дополнительных действий не требуется. Все методы и операторы работают как предполагается.
say (٤,٥,٦,1,2,3).sort; # (1 2 3 4 5 6)
say 1 + ٩; # 10
При использовании обычных операций над строками, мы можем получить не всегда ожидаемый результат, особенно при сравнении и сортировке.
say 'a' cmp 'B'; # More
Сравнение выше показывает, что a
больше, чем B
. Причина в том, что код символа прописной a
больше, чем код символа заглавной B
.
Хотя это технически корректно, такое поведение не всегда желательно.
К счастью, в Perl 6 есть методы и операции, которые реализуют Unicode Collation Algorithm.
Один из них это unicmp
, который повторяет показанный выше cmp
, но с учётом Юникода.
say 'a' unicmp 'B'; # Less
Как вы видите, используя оператор unicmp
мы получаем ожидаемый результат, в котором a
меньше, чем B
.
Как альтернатива методу sort
, который сортирует на основе кодов символов, в Perl 6 есть метод collate
,
который реализует 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)
Обычно, все задачи в программе выполняются последовательно.
Чаще всего это не является проблемой, если то, что вы пытаетесь сделать, не занимает много времени.
К счастью, в Perl 6 есть возможности, которые позволяют вам выполнять процессы параллельно.
На этом этапе важно отметить, что параллелизм может означать одно из двух понятий:
-
Параллелизм задач: Два (или больше) независимых выражения вычисляются параллельно.
-
Параллелизм данных: Единственное выражение обходит список элементов параллельно.
Давайте начнём со второго.
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
), сравните время, которое потребовалось для выполнения каждого скрипта.
Tip
|
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; Если вы запустите оба примера, то можете увидеть, что один из них отсортирован, а другой - нет. |
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;
-
Мы определили два массива
-
применили разные операции к каждому массиву и сохранили результаты
-
проверили, являются ли оба результата одинаковыми
Скрипт ждёт, пока выполнится @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
ждёт промис.
Если оно сдержано, то результат будут возвращён.
Если оно нарушено, будет брошено исключение.
Проверьте время, которое заняло выполнение каждого скрипта.
Warning
|
Параллелизм всегда добавляет дополнительную нагрузку ("оверхед") из-за многопоточности. Если эта нагрузка не покрывается приростом скорости вычисления, скрипт будет работать медленнее. Именно поэтому, использование race , hyper , start и await для очень простых скриптов на деле может сделать их медленнее.
|
Note
|
Больше узнать об одновременности и асинхронности в программировании можно узнать на https://docs.perl6.org/language/concurrency |
В Perl 6 есть возможность использовать библиотеки языка Си, используя интерфейс для нативных вызовов.
NativeCall
это стандартный модуль, который поставляется с Perl 6 и предоставляет набор функциональности для того,
чтобы упростить взаимодействие между Perl 6 и Си.
Рассмотрим код на языке Си, который определяет функцию под названием hellofromc
.
Эта функция выводит в терминал Hello from C
. Она не принимает никаких аргументов и не возвращает никакого значения.
#include <stdio.h>
void hellofromc () {
printf("Hello from C\n");
}
В зависимости от вашей операционной системы, запустите следующие команды для того, чтобы скомпилировать код на Cи в библиотеку.
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
В той же директории, где вы скомпилировали Си библиотеку, создайте новый скрипт на Perl 6, который содержит код ниже, и запустите его.
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub hellofromc() is native(LIBPATH) { * }
hellofromc();
Сперва мы объявили, что будем использовать модуль NativeCall
.
После этого мы создали константу LIBPATH
, которая содержит путь к Си библиотеке.
Отметьте, что $*CWD
возвращает текущую рабочую директорию.
После этого мы создали подпрограмму под названием hellofromc()
, которая служит
обёрткой для своей дополняющей функции на Си, с таким же именем и находящуюся в Си
библиотеке, которую можно найти в пути LIBPATH
.
Всё это сделано используя трейт is native
.
Наконец, мы вызываем нашу подпрограмму в Perl 6.
В сущности, всё сводится к объявлению подпрограммы с трейтом is native
и именем Си библиотеки.
В подразделе выше, мы видели, как можно вызвать очень простую функцию на Си, обернув
её с помощью подпрограммы в Perl 6 с таким же именем, используя трейт is native
.
В некоторых случаях, может быть предпочтительно изменить имя подпрограммы Perl 6.
Для этого, мы используем трейт is symbol
.
Давайте изменим скрипт выше на Perl 6 и назовём Perl 6 подпрограмму hello
вместо hellofromc
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub hello() is native(LIBPATH) is symbol('hellofromc') { * }
hello();
В случае, когда подпрограмма в Perl 6 имеет имя отличное от своего Си дополнения, нам
следует использовать трейт is symbol
с именем оригинальной функции на Си.
Скомпилируйте код модифицированной библиотеки Си и запустите скрипт на Perl 6, которые указаны ниже.
Отметьте, что мы изменили и код на Си и код на Perl 6 для того, чтобы принимать аргументом строку (char*
в Cи и Str
и Perl 6)
#include <stdio.h>
void hellofromc (char* name) {
printf("Hello, %s! This is C!\n", name);
}
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub hello(Str) is native(LIBPATH) is symbol('hellofromc') { * }
hello('Jane');
Повторим процесс, создав простой калькулятор, который принимает
2 целых числа и складывает их.
Скомпилируйте Си библиотеку и запустите Perl 6 скрипт.
int add (int a, int b) {
return (a + b);
}
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub add(int32,int32) returns int32 is native(LIBPATH) { * }
say add(2,3);
Отметьте, что и функция на Си и функция на Perl 6 принимают два целых числа и возвращают одно
(int
в Cи и int32
в Perl 6)
Возможно, вы задаётесь вопросом, почему мы использовали int32
вместо Int
в последнем скрипте на Perl 6.
Часть типов в Perl 6, такие как Int
, Rat
и некоторые другие, не могут передаваться или быть возвращены значением из функции на Cи.
Необходимо использовать такие же типы в Perl 6, как в Cи.
К счастью, Perl 6 предоставляет много типов для того, чтобы представлять соответствующий тип в Cи.
Тип в Cи | Тип в Perl 6 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Массивы: к примеру, |
|
Note
|
Больше об интерфейсе для нативных вызовов можно узнать на https://docs.perl6.org/language/nativecall |
-
#perl6 IRC канал. Много вещей обсуждается на IRC. Скорее всего, это первое место, куда вам следует обращаться по любому вопросу: https://perl6.org/community/irc
-
p6weekly это еженедельный обзор изменений в Perl 6 и связанных с ним областях.
-
pl6anet агрегатор блогов. Оставайтесь в курсе, читая блоги, которые сфокусированы на Perl 6.
-
/r/perl6 подписывайтесь на подраздел Perl 6.
-
@perl6org следите за нами в Twitter.
-
P6lert - уведомления от Perl 6 Core Developers. Будьте в курсе всех важных изменений.