Вчера мы говорили об обратном слеше, который связывает парметры функции с данными, переданными при вызове. Сегодня мы продолжаем тему и поговорим о тех модификаторах (trait), которыми могут быть снабжены параметры.
Начнем с простого кода:
sub f($x) {
$x++;
say $x;
}
my $v = 42;
f($v);
say $v;
Этот код вполне бы работал в Perl 5.20 (если добавить use feature 'signatures'), но в Perl 6 он завершается с ошибкой:
Cannot resolve caller postfix:<++>(Int); the following candidates
match the type but require mutable arguments:
(Mu:D $a is rw)
(Int:D $a is rw)
The following do not match for other reasons:
(Bool:D $a is rw)
(Bool:U $a is rw --> Bool::False)
(Mu:U $a is rw)
(Num:D $a is rw)
(Num:U $a is rw)
(int $a is rw)
(num $a is rw --> num)
in sub f at args-1.pl line 2
in block at args-1.pl line 7
Сообщение об ошибке большое, но не сообщает о главном: по умолчанию аргументы функции разрешены только для чтения. Если убрать инкремент $x++, то все заработает:
42 42
Параметры в сигнатуре функции могут содержать дополнительные пометки со словом is. Если пометки нет, это все равно что есть is readonly, и, конечно же, понятно, что такую переменную изменить нельзя:
sub f($x is readonly) {
# $x++;
say $x;
}
Хотите что-то менять — передавайте копию переменной (is copy) или разрешайте ее изменять явно (is rw).
sub f($x is copy) {
$x++;
say $x;
}
sub g($x is rw) {
$x++;
say $x;
}
my $v = 42;
f($v); # 43
say $v; # 42
g($v); # 43
say $v; # 43
Понятно, что если передать константу, то изменить ее не получится:
f(42); # 43 g(42); # ошибка
Ошибка сообщает о том, что функция получила целое значение вместо изменяемой переменной (вполне себе сочетаемые слова).
Parameter '$x' expected a writable container, but got Int value
in sub g at args-1.pl line 6
in block at args-1.pl line 19
Наконец, атрибут is raw это то же что и \, но только при этом переменные не лишаются сигила:
sub h($x is raw) {
$x++;
say $x;
}
my $n = 42;
h($n); # 43
say $n; # 43
# h(42); # ошибка
Как видите, возможностей весьма много, но это лишь часть того, на что способны сигнатуры функций в Perl 6.
Так чем всё-таки отличаются is raw и is rw?
Raw может привязываться к значениям, а rw нет:
sub f($x is raw) { say $x.WHAT; } my $n = 42; f($n); f(42); sub g($y is rw) { say $y.WHAT; } my $m = 42; g($m); # g(42); # errorО, спасибо! Получается, что константа просто не пройдёт сигнатуру rw.
Сигнатуру как раз «проходит» 🙂 Ошибка в рантайме.
Я наверно плохо выразился. 42 в рантайме не проходит сигнатуру, даже несмотря на то, что сабрутина не пытается поменять значение.
Ну да, это происходит во время попытки привязать (:=) параметр:
# Binds a single parameter. sub bind_one_param($lexpad, $sig, $param, int $no_nom_type_check, $error, int $got_native, $oval, int $ival, num $nval, str $sval) { . . . elsif $is_rw { if nqp::isrwcont($oval) { nqp::bindkey($lexpad, $varname, $oval) if $has_varname; } else { if nqp::defined($error) { my %ex := nqp::gethllsym('perl6', 'P6EX'); if nqp::isnull(%ex) || !nqp::existskey(%ex, 'X::Parameter::RW') { $error[0] := "Parameter '$varname' expected a writable container, but got an " ~ ~ $oval.HOW.name($oval) ~ " value"; } else { $error[0] := { nqp::atkey(%ex, 'X::Parameter::RW')($oval, $varname) }; } } return $BIND_RESULT_FAIL; } }Хотя наверное можно было отловить и при компиляции.
В смысле юзер может отловить или компилятор мог бы отлавливать?
Компилятор.