Как написать персонализированные методы доступа в Perl6?
Как написать персонализированные методы доступа в Perl6?
Если у меня есть этот класс:
class Wizard {
has Int $.mana is rw;
}
Я могу это сделать:
my Wizard $gandalf .= new;
$gandalf.mana = 150;
Скажем, я хочу добавить небольшую проверку сеттера в свой класс Perl6, не отказываясь от нотации $gandalf.mana = 150;
(другими словами, я не хочу писать это: $gandalf.setMana(150);
). Программа должна умереть, если она пытается установить отрицательную ману. Как мне это сделать? Документация Perl6 просто упоминает, что можно писать пользовательские аксессоры, но не говорит, как это сделать.
Ответы
Ответ 1
Вы можете получить тот же интерфейс доступа, который говорит $.mana
, объявив метод is rw
. Затем вы можете обернуть прокси-сервер вокруг базового атрибута следующим образом:
#!/usr/bin/env perl6
use v6;
use Test;
plan 2;
class Wizard {
has Int $!mana;
method mana() is rw {
return Proxy.new:
FETCH => sub ($) { return $!mana },
STORE => sub ($, $mana) {
die "It over 9000!" if ($mana // 0) > 9000;
$!mana = $mana;
}
}
}
my Wizard $gandalf .= new;
$gandalf.mana = 150;
ok $gandalf.mana == 150, 'Updating mana works';
throws_like sub {
$gandalf.mana = 9001;
}, X::AdHoc, 'Too much mana is too much';
Proxy
- это в основном способ перехвата вызовов чтения и записи в хранилище и выполнять что-то другое, кроме поведения по умолчанию. Как предполагает их капитализация, FETCH
и STORE
автоматически вызывается Perl для разрешения выражений типа $gandalf.mana = $gandalf.mana + 5
.
Здесь более полное обсуждение, включая вопрос о том, следует ли даже попытаться это сделать, в PerlMonks. Я бы рекомендовал против вышеперечисленных и общедоступных атрибутов rw
в целом. Это скорее отображение того, что можно выразить на языке, чем полезный инструмент.
Ответ 2
В более поздних версиях Rakudo есть подмножество с именем UInt
, которое ограничивает его положительными значениями.
class Wizard {
has UInt $.mana is rw;
}
Чтобы вы не застревали, если вам нужно что-то подобное; вот как это определено:
(вы можете оставить my
, но я хотел показать вам фактическую строку из источника Rakudo)
my subset UInt of Int where * >= 0;
Вы также можете сделать это:
class Wizard {
has Int $.mana is rw where * >= 0;
}
Я хотел бы указать, что ограничение * >= 0
в where
является всего лишь коротким способом создания Callable.
В качестве ограничения where
вы можете использовать любое из следующих условий:
... where &subroutine # a subroutine that returns a true value for positive values
... where { $_ >= 0 }
... where -> $a { $a >= 0 }
... where { $^a >= 0 }
... where $_ >= 0 # statements also work ( 「$_」 is set to the value it testing )
(Если вы хотите, чтобы он просто не равнялся нулю, вы также могли бы использовать ... where &prefix:<?>
, который, вероятно, лучше пишется как ... where ?*
или ... where * !== 0
)
Если вам кажется, что вы раздражаете людей, использующих ваш код, вы также можете это сделать.
class Wizard {
has UInt $.mana is rw where Bool.pick; # accepts changes randomly
}
Если вы хотите удостовериться, что значение "имеет смысл" при просмотре всех значений в классе в совокупности, вам нужно будет работать намного больше.
(Это может потребовать гораздо больше знаний о реализации)
class Wizard {
has Int $.mana; # use . instead of ! for better `.perl` representation
# overwrite the method the attribute declaration added
method mana () is rw {
Proxy.new(
FETCH => -> $ { $!mana },
STORE => -> $, Int $new {
die 'invalid mana' unless $new >= 0; # placeholder for a better error
$!mana = $new
}
)
}
}