Как написать персонализированные методы доступа в 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
      }
    )
  }
}