/
basics.pod
350 lines (209 loc) · 28.1 KB
/
basics.pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
=begin pod
=NAME Базовый синтаксис
Изначальным предназначением Perl была обработка текстовых файлов. Это предназначение по прежнему является важным, однако Perl 5 также является мощным языком программирования общего назначения. Perl 6 является еще более развитым.
Представьте, что вы устраиваете турнир по настольному теннису. Рефери сообщают результаты соревнований в формате C<Player 1 vs Player 2 | 3:2>, то есть участник C<Player 1> выиграл у C<Player 2> три сета против двух. Для определения победителя создадим скрипт, который просуммирует количество выигранных матчей и сетов для каждого игрока.
Входные данные выглядят следующим образом:
Beth Ana Charlie Dave
Ana vs Dave | 3:0
Charlie vs Beth | 3:1
Ana vs Beth | 2:3
Dave vs Charlie | 3:0
Ana vs Charlie | 3:1
Beth vs Dave | 0:3
Первая строка содержит список игроков, а каждая последующая - результаты матчей.
Один из способов получить ожидаемый результат с помощью Perl 6 следующий:
=begin code
use v6;
my $file = open 'scores';
my @names = $file.get.split(' ');
my %matches;
my %sets;
for $file.lines -> $line {
my ($pairing, $result) = $line.split(' | ');
my ($p1, $p2) = $pairing.split(' vs ');
my ($r1, $r2) = $result.split(':');
%sets{$p1} += $r1;
%sets{$p2} += $r2;
if $r1 > $r2 {
%matches{$p1}++;
} else {
%matches{$p2}++;
}
}
my @sorted = @names.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse;
for @sorted -> $n {
say "$n has won %matches{$n} matches and %sets{$n} sets";
}
=end code
На экран будет выведен следующий результат:
=begin output
Ana has won 2 matches and 8 sets
Dave has won 2 matches and 6 sets
Charlie has won 1 matches and 4 sets
Beth has won 1 matches and 4 sets
=end output
Каждая программа на Perl 6 начинается с C<use v6;>X<|v6>. Эта строка указывает компилятору необходимую версию Perl. Благодаря ей, при случайной попытке выполнить файл с помощью Perl 5, появиться полезное сообщение об ошибке.
В программе на Perl 6 может быть как ни одной, так и произвольное количество команд (X<утверждений|утверждение>). I<Команда> завершается точкой с запятой или фигурной скобкой в конце строки:
=begin code
my $file = open 'scores';
=end code
В данной строке C<my> определяет X<лексическую|переменные> переменную. Лексическая переменная доступна только в границах текущего X<блока|блок>. Если границы не определены, то видимость распространяется до конца файла. Блок - любая часть кода ограниченная фигурными скобками { }.
Имя переменной начинается с I< X<сигила|сигил;sigil;магия> > - символа (значка), обладающего по утверждению wikipedia I<( и тут я полностью согласен )> определенной магической силой. В Perl 6 к сигилам относятся такие символы, как C<$>, C<@>, C<%> и C<&> ( изредка встречающийся в виде пары двоеточий C<::> ).
Сигилы наделяют переменную особыми характеристиками, наподобие возможности хранения простого или составного значения. После сигила следует X<идентификатор>, состоящий из букв, цифр и символов подчеркивания. Между буквами возможно использование дефиса C<-> или апострофа C<'>, поэтому C<isn't> и C<double-click> являются допустимыми именами.
Сигил C<$> указывается перед I<X<скалярной|скаляр, переменная; скаляр>> переменной. Эти переменные могут хранить одиночное значение.
Встроенная функция C<open> открывает файл с именем I<scores> и возвращает I<X<дескриптор файла|присвоение; дескриптор, файл>> - объект ассоциированный с указанным файлом. Знак равенства C<=>I<присваивает> дескриптор переменной слева и является способом сохранения дескриптора файла в переменной C<$file>.
C<'scores'> является I<X< строковым литералом |сроковый, литерал; строка >> . Строка является участком текста, в строковый литерал - строкой объявленной непосредственно в программе. В следующей строке строковый литерал указан в качестве аргумента для функции C<open>.
=begin code
my @names = $file.get.split(' ');
=end code
В данной строке виден правосторонний способ вызова I<X< методов|метод>> - именованного набора команд. Так у хранящегося в переменной C<$file> дескриптора файла вызывается метод C<get>. Метод C<get> читает и возвращает строку из файла, удаляя символ конца строки I<( я предполагаю, что это перевод каретки)>. Далее следует вызов метода C<split>. Он вызывается для строки, возвращаемой C<get>. Эту строку называют I<X< инвокантом (invocant)|invocant;метод>>. Метод C<split> используется для разбиения строки-инвоканта на части, используя в качестве разделителя шаблон. Шаблон передается через аргумент. В нашем случае в качестве аргумента C<split> получает строку, состоящую из символа пробела.
Таким образом строка из нашего примера C<'Beth Ana Charlie Dave'> будет преобразована в список небольших строк: C<'Beth', 'Ana', 'Charlie', 'Dave'>. А затем сохранена I<(присвоена)> в X<массив> C<@names>. Сигил C<@> маркирует указанную переменную как C<Array>I<(Массив)>. Массивы хранят упорядоченные списки.
=begin para :sidebar :todo('proper cross-link to regex')
Разделение по пустому символу не оптимально, не дает ожидаемого результата при наличии пробелов в конце строки или больше одного пробела в столбце данных наших соревнований. Для подобных задач наиболее подойдут способы извлечения данных в разделе посвященном регулярным выражениям.
=end para
=begin code
my %matches;
my %sets;
=end code
Указанные две строки кода определяют два X<хэша|хэш>. Сигил C<%> помечает каждую из переменных как C<Hash> I<(Хэш)>. Хэш представляет собой неупорядоченный набор пар ключей и значений. В других языках программирования можно встретить другие названия для данного типа: I<hash table>,
I<dictionary> или I<map>. Получение из хэш-таблицы значения соответствующего запрашиваемому ключу C<$key> производиться посредством выражения C<%hash{$key}>.
=para :сноска
В отличие от Perl 5, в Perl 6 сигил остается неизменным при обращении к массива или хэшам с использованием C<[ ]> or C<{ }>. Данная особенность называется I<X<постоянство сигила (sigil invariance)|sigil invariance; постоянство сигила>>.
В программе расчета рейтингов матча, C<%matches> хранит число выигранных матчей каждым игроком. В C<%sets> запоминаются выигранные каждым игроком сеты.
Сигилы указывают на метод доступа к значениям. Переменные с сигилом C<@> предоставляют доступ к значениям по номеру позиции; переменные с сигилом C<%> - по строковому ключу. Сигил C<$>, обычно, ассоциируется с общим контейнером, которым может содержать что угодно и доступ к данным так же может быть организован любым образом. Это значит, что скаляр может даже содержать составные объекты C<Array> и C<Hash>; сигил C<$> указывает на тот факт, что данная переменная должна интерпретироваться как одиночное значение, даже в контексте где ожидаются множественные (как например C<Array> и C<Hash> ).
=begin code
for $file.lines -> $line {
...
}
=end code
Оператор C<for>X<|for> создает цикл, выполняющий I<X<блок>> кода, ограниченный фигурными скобками содержащий C<...>, для каждого элемента в списке. Перед каждой итерацией переменная C<$line> устанавливается в очередную строку, прочитанную из файла. C<$file.lines> возвращает список строк из файла I<scores>, начиная со строки, следующей за последней прочитанной C<$file.get>. Чтение продолжается пока ну будет достигнут конец файла.
При первой итерации, C<$line> будет содержать строку C<Ana vs Dave | 3:0>. При второй - C<Charlie vs Beth | 3:1>, и так далее.
=begin code
my ($pairing, $result) = $line.split(' | ');
=end code
С помощью C<my> можно определить сразу несколько переменных одновременно. В правой части присвоения снова встречаем вызов вызов метода C<split>, но в этот раз в качестве разделителя используется вертикальная черта с пробелами вокруг. Переменная C<$pairing> получает значение первого элемента возвращаемого списка, а C<$result> - второе.
В нашем примере, после обработки первой строки C<$pairing> будет содержать строку C<Ana vs Dave> и C<$result> - C<3:0>.
Следующие пару строк демонстрируют тот же прием:
=begin code
my ($p1, $p2) = $pairing.split(' vs ');
my ($r1, $r2) = $result.split(':');
=end code
В первой строке извлекаются и сохраняются имена двух игроков в переменные C<$p1> и C<$p2>. В следующей строке примера результаты для каждого игрока сохраняются в переменные C<$r1> и C<$r2>.
После обработки первой строки файла переменные принимают следующие значения:
=config C<> :allow<E>
=for table :caption('Содержимое переменных')
Переменная Значение
------------|----------
C<$line> C<'Ana vs Dave E<VERTICAL LINE> 3:0'>
C<$pairing> C<'Ana vs Dave'>
C<$result> C<'3:0'>
C<$p1> C<'Ana'>
C<$p2> C<'Dave'>
C<$r1> C<'3'>
C<$r2> C<'0'>
Программа подсчитывает количество выигранных сетов каждым игроком в следующих строках:
=begin code
%sets{$p1} += $r1;
%sets{$p2} += $r2;
=end code
Приведенные строки кода представляют собой сокращенную форму более общей:
=begin code
%sets{$p1} = %sets{$p1} + $r1;
%sets{$p2} = %sets{$p2} + $r2;
=end code
Выражение C<+= $r1> означает I<увеличение значения в переменной, расположенной слева, на величину $r1> X<|+=;+=, операторы>. Предыдущее значение суммируется с C<$r1> и результат сохраняется в переменную слева. При выполнении первой итерации C<%sets{$p1}> имеет особое значение и по умолчанию оно равно специальному значению C<Any>X<|Any>. При выполнении арифметических операций C<Any> трактуется как число со значением 0.
Перед указанными выше двух строк кода, хэш C<%sets> пуст. При операциях сложения, отсутствующие ключи в хэше создаются в процессе выполнения, а значения равны 0. Это называется I<X<автовивификация (autovivification)|автовивификация;autovivification>>. При первом выполнении цикла после этих двух строк C<%sets> содержит C<< 'Ana' => 3, 'Dave' => 0 >>. ( Стрелка C<< => >> разделяет ключ от значения в C<Паре> I<(X<Pair|pair; пара>)>.)
=begin code
if $r1 > $r2 {
%matches{$p1}++;
} else {
%matches{$p2}++;
}
=end code
Если C<$r1> имеет большее значение чем C<$r2>, содержимое C<%matches{$p1}> увеличивается на единицу. Если C<$r1> не больше чем C<$r2>,увеличивается на единицу C<%matches{$p2}>. Также как в случае с C<+=>, если в хэше отсутствовал ключ, он будет атовивифицирован I<( это слово приходится даже проговаривать вслух, чтобы написать )> оператором инкремента.
X<|postincrement;операторы, постинкремент;операторы, преинкремент;постинкремент;преинкремент>
C<$thing++> - эквивалентен выражениям C<$thing += 1> или C<$thing = $thing + 1>, и представляет собой более краткую их форму, но с небольшим исключением: он возвращает значение C<$thing> I<предшествующее> увеличению на единицу. Если, как во многих языках программирования, используется C<++> как префикс, то возвращается результат, т.е. увеличенное на единицу значение. Так C<my $x = 1; say ++$x> выведет на экран C<2>.
=begin code
my @sorted = @names.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse;
=end code
Данная строка содержит три самостоятельных шага. Метод массива C<sort> возвращает отсортированную копию содержимого массива. Однако, по умолчанию сортировка производиться по содержимому. Для нашей задачи необходимо сортировка не по имени игроков, а по их победам. Для указания критерия сортировки методу C<sort> передается I<блок>, который преобразует массив элементов (в данном случае имена игроков) в данные для сортировки. Имена элементов передаются в I<блок> через I<X<локальную переменную|локальная переменная; переменные, $_>>.
Блоки встречались и ранее: в цикле C<for> использовался C<< -> $line { ... } >>, а также при сравнении C<if>. X<Блок> - самодостаточный кусок кода Perl 6 с необязательной сигнатурой ( а именно C<< -> $line >> в примере для C<for>). Подробнее описано в разделе посвященном L<сигнатурам>.
Наиболее простым способом сортировки игроков по достигнутым результатам будет код C<@names.sort({%matches{$_} })>, который сортирует по выигранным матчам. Однако Ana и Dave оба выиграли по два матча. Поэтому, для определения победителей в турнире, требуется анализ дополнительного критерия - количества выигранных сетов.
Когда два элемента массива имеют одинаковые значения, C<sort> сохраняет их оригинальный порядок следования. В компьютерной науке данному поведению соответствует термин
I<X<устойчивая сортировка (stable sort)|устойчивая сортировка;stable sort; сортировка, устойчивая>>. Программа использует эту особенность метода C<sort> языка Perl 6 для получения результата, применяя сортировку дважды: сначала сортируя игроков по количеству выигранных сетов (второстепенный критерий определения победителя), а затем - по количеству выигранных матчей.
После первой сортировки имена игроков располагаются в следующем порядке: C<Beth Charlie Dave Ana>. После второго шага данный порядок сохраняется. Связано с тем, что количество выигранных сетов связаны в той же последовательности, что и числовой ряд выигранных матчей. Однако, при проведении больших турниров возможны исключения.
C<sort> производит сортировку в восходящем порядке, от наименьшего к большему. В случае подготовки списка победителей необходим обратный порядок. Вот почему производится вызов метода C<.reverse> после второй сортировки. Затем список результатов сохраняется в C<@sorted>.
=begin code
for @sorted -> $n {
say "$n has won %matches{$n} matches and %sets{$n} sets";
}
=end code
Для вывода результатов турнира, используется цикл по массиву C<@sorted>, на каждом шаге которого имя очередного игрока сохраняется в переменную C<$n>. Данный код можно прочитать следующим образом: "Для каждого элемента списка sorted: установить значение переменной C<$n> равное текущему элементу списка, а затем выполнить блок". Команда C<say> X<|операторы,say;say> выводит аргументы на устройство вывода I<(обычно это - экран)> и завершает вывод переводом курсора на новую строку. Чтобы вывести на экран без перевода курсора в конце строки, используется оператор C<print> X<|операторы,print;print>.
В процессе работы программы, на экране выводится не совсем точная копия строки, указанной в параметрах C<say>. Вместо C<$n> выводится содержимое переменной C<$n> - имена игроков. Данная автоматическая подстановка значения переменой вместо ее имени называется I<X<интерполяцией| интерполяция>>. Интерполяция производится в строках, заключенных в двойные кавычки C<"...">. А в строках с одинарными кавычками C<'...'> - нет.
X<|строки;одиночные кавычки>
=begin code
my $names = 'things';
say 'Do not call me $names'; # Do not call me $names
say "Do not call me $names"; # Do not call me things
=end code
В X<заключенных в двойные кавычки| строки,двойные кавычки; строки> строках Perl 6 может интерполировать не только переменные с сигилом C<$>, но и блоки кода в фигурных скобках. Поскольку любой код Perl может быть указан в фигурных скобках, это делает возможным интерполировать переменные с типами C<Array> и C<Hash>. Достаточно указать необходимую переменную внутри фигурных скобок.
Массивы внутри фигурных скобок интерполируются в строку с одним пробелом в качестве разделителя элементов. Хэши, помещенные в блок, преобразуются в очередность строк. Каждая строка содержит ключ и соответствующее ему значение, разделенные табуляцией. Завершается строка символом новой строки I<( он же перевод каретки, или newline )>
=begin code
say "Math: { 1 + 2 }" # Math: 3
my @people = <Luke Matthew Mark>;
say "The synoptics are: {@people}" # The synoptics are: Luke Matthew Mark
say "{%sets}"; # From the table tennis tournament
# Charlie 4
# Dave 6
# Ana 8
# Beth 4
=end code
Когда переменные с типом массив или хэш встречаются непосредственно в строке, заключенной в двойные кавычки, но не в внутри фигурных скобок, они интерполируются, если после имени переменной находится X<postcircumfix> - скобочная пара следующая за утверждением. Примером может служить обращение к элементу массива: C< @myarray[1]>. Интерполяция производится также, если между переменной и postcircumfix находятся вызовы методов.
X<|Zen slice>
=begin code
my @flavours = <vanilla peach>;
say "we have @flavours"; # we have @flavours
say "we have @flavours[0]"; # we have vanilla
# so-called "Zen slice"
say "we have @flavours[]"; # we have vanilla peach
# method calls ending in postcircumfix
say "we have @flavours.sort()"; # we have peach vanilla
# chained method calls:
say "we have @flavours.sort.join(', ')";
# we have peach, vanilla
=end code
=head2 Упражнения
B<1.> Входной формат данных для рассмотренного примера избыточен: первая строка содержит имена всех игроков, что излишне. Имена участвующих в турнире игроков можно получить из последующих строк.
Как изменить программу если строка с именами игроков отсутствует ?
Подсказка: C<%hash.keys> возвращает список всех ключей C<%hash>.
B<Ответ:> Достаточно удалить строку C<my @names = $file.get.split(' ');>, и внести изменения в код:
=begin code
my @sorted = @names.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse;
=end code
... чтобы стало:
=begin code
my @sorted = B<%sets.keys>.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse;
=end code
B<2.> Вместо удаления избыточной строки, ее можно использовать для контроля наличия всех упомянутых в ней игроков среди результатов матча. Например, для обнаружения опечаток в именах. Каким образом можно изменить программу, чтобы добавить такую функциональность ?
B<Ответ:> Ввести еще один хэш, в котором хранить в качестве ключей правильные имена игроков, а затем использовать его при чтении данных сетов:
=begin code :allow<B>
...
my @names = $file.get.split(' ');
my %legitimate-players;
for @names -> $n {
%legitimate-players{$n} = 1;
}
...
for $file.lines -> $line {
my ($pairing, $result) = $line.split(' | ');
B<< my ($p1, $p2) = $pairing.split(' vs ');
for $p1, $p2 -> $p {
if !%legitimate-players{$p} {
say "Warning: '$p' is not on our list!";
}
}
>>
...
}
=end code
=end pod