Как вы можете динамически генерировать значения для использования с чертами? - PullRequest
7 голосов
/ 02 ноября 2019

Для библиотеки, которую я пишу, у меня есть атрибут в HOW, который использует черту handles для делегирования методов различных ролей, выполняемых другим HOW, который он использует, для экземпляра этого HOW. Моя первая попытка выглядела так (хотя, чтобы сделать это легче для чтения, это будет иметь дело только с Metamodel::Naming):

class ParentHOW does Metamodel::Naming {
    method new_type(ParentHOW:_: Str:D :$name!, Str:D :$repr = 'P6opaque' --> Mu) {
        my ::?CLASS:D $meta := self.new;
        my Mu         $type := Metamodel::Primitives.create_type: $meta, $repr;
        $meta.set_name: $type, $name;
        $type
    }
}

class ChildHOW {
    has Mu $!parent;
    has Mu $!parent_meta handles <set_name shortname set_shortname>;

    submethod BUILD(ChildHOW:D: Mu :$parent is raw) {
        $!parent      := $parent;
        $!parent_meta := $parent.HOW;
    }

    method new_type(ChildHOW:_: Mu :$parent is raw) {
        my ::?CLASS:D $meta := self.new: :$parent;
        Metamodel::Primitives.create_type: $meta, $parent.REPR
    }

    method name(ChildHOW:D: Mu \C --> Str:_) { ... }
}

my Mu constant Parent = ParentHOW.new_type: :name<Parent>;
my Mu constant Child  = ChildHOW.new_type:  :parent(Parent);

say Child.^shortname; # OUTPUT: Parent

Проблема с этим заключается в том, если какой-либо из типов, которые я делаю, это КАК обрабатывать методынавсегда изменения, это больше не будет работать со всеми их методами. Поэтому вместо этого я хочу динамически генерировать список методов, которые должны быть обработаны, учитывая список методов, которые это КАК переопределяет, и список типов, чьи методы должны быть обработаны. Это не так просто, как кажется, из-за того, как реализована черта handles. Например, это не сработает:

has Mu $!parent_meta handles do {
    my Array[Str:D] constant PARENT_METHOD_OVERRIDES .= new: <name>;

    ((), Metamodel::Naming)
        .reduce({ (|$^methods, |$^role.HOW.methods: $^role) })
        .map(*.name)
        .grep(PARENT_METHOD_OVERRIDES ∌ *)
};

Итак, как бы вы динамически сгенерировали значение для черты для использования в подобных случаях?

1 Ответ

6 голосов
/ 02 ноября 2019

Приложение Trait устанавливается во время компиляции, поэтому нам нужен способ генерирования значения, которое оно будет использовать и во время компиляции. Это можно сделать с помощью фазера BEGIN, но в этом случае лучше писать с помощью constant.

Константы в Raku - это не просто переменные, которые вы не можете назначать или связывать после их объявления. Обычно, когда вы объявляете переменную, ее символ устанавливается во время компиляции, но его значение фактически не устанавливается до времени выполнения, поэтому это может произойти:

my Int:D $foo = 1;

BEGIN say $foo; # OUTPUT: (Int)

Это не так с constant;компилятор устанавливает значение символа во время компиляции. Это означает, что для примера в вопросе мы можем динамически сгенерировать список методов для handles для использования следующим образом:

my Array[Str:D] constant PARENT_METHOD_OVERRIDES .= new: <name>;
my Array[Str:D] constant PARENT_METHODS          .= new:
    ((), Metamodel::Naming)
        .reduce({ (|$^methods, |$^role.HOW.methods: $^role) })
        .map(*.name)
        .grep(PARENT_METHOD_OVERRIDES ∌ *);

has Mu $!parent;
has Mu $!parent_meta handles PARENT_METHODS;

Если по какой-то причине символы, такие как PARENT_METHOD_OVERRIDES и PARENT_METHODS mustnне существует в контексте типа, вы все равно можете обрабатывать признаки таким образом, объявляя константы и добавляя атрибуты из замыкания;объявления методов и атрибутов ограничены таким образом, что вы можете написать их из любого места в пакете типа и при этом добавить их в тип. Помните, что методы и атрибуты обрабатываются во время компиляции, поэтому вы не будете обрабатывать что-то вроде динамического генерирования атрибутов или методов для типа.

...