После нескольких лет перерыва в программировании я пытаюсь снова набрать скорость, изучая Perl 6. Эта серия статей задумана как своего рода отчет о прогрессе, демонстрирующий не только то, что я узнал, но и все мои недоразумения и ошибки.
Те из вас, кто знаком с Perl 5, знают, что он сильно отличается от большинства других языков при объявлении подпрограмм. При объявлении подпрограммы / метода вы вообще не объявляете параметры.
Другие языки, подобные C, заставляют вас делать это; даже Python делает. Но когда дело доходит до таких вещей, Perl 5 придерживается принципа невмешательства:
sub a_sub { # You can call this method with an arbitrary # amount of parameters. They will all be stored # in the array @_. print $_[0] + $_[0]; } &a_sub(1, 2); # Output: 3
Это нормально, если вы единственный программист, но если вы команда или через некоторое время пересматриваете свой собственный код, под-объявление дает несколько подсказок или подсказок как для интерпретатора, так и для других программистов о том, как вызывать it: вы не знаете, сколько параметров принимает метод, и какие типы параметров должны быть (например, целые числа, строки, даты и т. д.).
Кроме того, нет ничего, что мешало бы вам изменить значения параметров внутри самого подпрограммы.
sub a_sub { $_[1] *= 2; print $_[0] + $_[1]; } &a_sub(1, 2); # Output: 5
Так что по замыслу вы можете нанести здесь вред.
Perl 6 держит вашу руку несколько больше - если вы захотите. Как и раньше, массив @_ доступен и в Perl 6, но вы также можете объявить подпись более явно:
sub a-sub($param1, $param2) { say $param1 + $param2; } a-sub(1, 2); # Output: 3
Вы даже можете ввести их:
sub a-sub(Int $param1, Int $param2) { say $param1 + param2; } a-sub(1, 2); # Output: 3 a-sub(1.01, 2); # Throws an error
Пока что ничем не отличается от других C-подобных языков. Но теперь начинается самое интересное!
sub a-sub(Int $param1 where * > 0, Int $param2 where * % 2 == 0) { say $param1 + param2; } a-sub(1, 2); # Output: 3 a-sub(0, 2); # Throws an error because $param1 has to be > 0 a-sub(1, 3); # Throws an error because $param2 has to be even
Таким образом, вы можете не только проверять типы, но и определять ограничения. На других языках вы должны программировать проверки, генерировать исключения и создавать обработку исключений, чтобы получить такие функции. Здесь вы получаете все это, а также сообщения об ошибках, которые пытаются указать, в чем заключается ваша ошибка (если таковая имеется).
Вы даже можете установить ограничения на основе ранее объявленных параметров, например:
sub a-sub(Int $param1, Int $param2 where * > $param1) { say $param1 + $param2; } a-sub(1, 2); # Output: 3 a-sub(2, 1); # Throws an error
Мощная штука!
Но что произойдет, если вы попытаетесь манипулировать параметром внутри подпрограммы, т.е. как мы это делали в Perl 5, примере №2 выше?
sub a-sub($param1, $param2) { $param2 *= 2; say $param1 + $param2; } a-sub(1,2); # Throws an error: # Cannot assign to a readonly variable ($param2)
Но Perl 6 открыт для представления о том, что вы действительно знаете, что делаете. Так что, если вам действительно нужно такое поведение, вы можете получить его, явно указав интерпретатору, что вы хотите:
sub a-sub($param1, $param2 is copy) { $param2 *= 2; say $param1 + $param2; } a-sub(1,2); # Output: 5
В этом смысле Perl 6 противоположен многим другим языкам в том, что вы должны объявить что-то копией; в других случаях вы должны заявить об этом, если это не так.
А теперь другой сценарий. Допустим, вы хотите передать значение по ссылке, чтобы операции, выполняемые в подпрограмме, отражались переменной, которую вы использовали в качестве значения параметра.
sub a-sub($param1, $param2 is rw) { $param2 *= 2; say $param1 + $param2; } my $value = 2; a-sub(1, $value); # Output: 5 say $value; # Output: 4
Подпрограмма может даже включать подпрограммы, которые доступны только внутри этих подписок. Эти «частные» подпрограммы имеют доступ к переменным, определенным в контексте внешней подпрограммы:
sub a-sub($param1, $param2) { sub max-val() { return $param2 > $param1 ?? $param2 !! $param1; } say "Of these, " ~ max-val() ~ " is biggest, and sum is " ~ $param 1 + $param2; } a-sub(1, 2); # Output: Of these 2 is biggest, and sum is 3; max-val(); # Throws an error: Undefined routine
Несмотря на то, что @_ доступен, вы также можете явно объявить аналогичные массивы:
sub a-sub(*@a) { # What this really says: Store all, if any, # parameters in an array for me, i.e. = @_ say @a.join(", "); } a-sub(1, 2, 3, 4, 5); # Output: 1, 2, 3, 4, 5
Что хорошо в возможности самостоятельно объявить такой "slurpy" массив, так это то, что вы можете смешивать именованные и slurpy параметры:
sub a-sub($param1, $param2, *@a) { say "Sum is " ~ $param1 + $param2 ~ " and rest is " ~ @a.join(", "); } a-sub(1, 2, 3, 4, 5); # Output: Sum is 3 and rest is 3, 4, 5
Вы хотите вызвать что-либо из этого как именованные параметры? Конечно вы можете; вы даже можете задать для параметров значения по умолчанию, чтобы вам не приходилось передавать значения, если значения по умолчанию соответствуют вашим требованиям.
sub a-sub(:$param1 = 1, :$param2 = 2, *@a) { say "Sum is " ~ $param1 + $param2 ~ " and rest is " ~ @a.join(", "); } a-sub(param1 => 1, param2 => 2, 3, 4, 5); a-sub(3, 4, 5); # Both output: Sum is 3 and rest is 3, 4, 5
Вы даже можете сделать их жидкими хешами:
sub a-sub(:$a1, *%h) { say %h; } a-sub(a1=>1, a2=>2, a3=>3, a4=>4); # Output: {a2 => 2, a3 => 3, a4 => 4}
И наконец ... несколько подписок с одинаковым именем, но с разными подписями:
multi a-sub($param1, $param2) { say "Sub 1: " ~ $param1 + $param2; } multi a-sub($param1, $param2, $param3) { say "Sub 2: " ~ $param1 + $param2 + $param3; } multi a-sub(*@params) { say "Sub 3: " ~ [+] @params; } a-sub(1, 2); # Output: Sub 1: 3 a-sub(1, 2, 3); # Output: Sub 2: 6 a-sub(1, 2, 3, 4, 5, 6); # Output: Sub 3: 21 a-sub(1..7); # Output: Sub 3: 28
Возможностей много, и ошеломляющие. Но при правильном использовании они в сумме превосходят то, что вы использовали раньше, или даже превосходят его. Я, например, люблю это.