Como você pode gerar valores dinamicamente para uso com características?

8

Para uma biblioteca que estou escrevendo, tenho um atributo em um HOW que usa a handlescaracterística para delegar métodos de várias funções executadas por outro COMO ele usa para uma instância desse HOW. Minha primeira tentativa foi assim (embora para facilitar a leitura, isso só lide com isso 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

O problema com isso é que, se algum dos tipos que eu fizer este COMO manipular métodos para sempre mudar, isso não funcionará mais com todos os seus métodos. Então, em vez disso, quero gerar dinamicamente uma lista de métodos que devem ser manipulados, dada uma lista de métodos que este COMO substitui e uma lista de tipos cujos métodos devem ser manipulados. Isso não é tão fácil quanto parece devido à maneira como a handlescaracterística é implementada. Por exemplo, isso não funcionará:

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 ∌ *)
};

Então, como você geraria dinamicamente um valor para uma característica usar em casos como esse?

Kaiepi
fonte

Respostas:

7

O aplicativo Trait é configurado durante a compilação, portanto, precisamos de uma maneira de gerar um valor para ser usado também durante a compilação. Isso pode ser feito usando o BEGINphaser, mas é melhor escrever constantneste caso.

As constantes no Raku não são apenas variáveis ​​às quais você não pode atribuir ou vincular depois de declará-las. Normalmente, quando você declara uma variável, seu símbolo é instalado durante a compilação, mas seu valor não é realmente definido até o tempo de execução, e é por isso que isso pode acontecer:

my Int:D $foo = 1;

BEGIN say $foo; # OUTPUT: (Int)

Este não é o caso com constant; o compilador define o valor do símbolo durante a compilação. Isso significa que, para o exemplo da pergunta, podemos gerar dinamicamente uma lista de métodos para handlesusar assim:

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;

Se, por algum motivo, símbolos como PARENT_METHOD_OVERRIDESe PARENT_METHODSnão existirem no contexto do tipo, você ainda pode manipular características dessa maneira declarando as constantes e adicionando os atributos de dentro de um fechamento; As declarações de método e atributo têm escopo definido de forma que você possa gravá-las de qualquer lugar dentro do pacote do tipo e ainda assim adicioná-las ao tipo. Lembre-se de que métodos e atributos são manipulados durante a compilação; portanto, não é assim que você lida com algo como gerar dinamicamente atributos ou métodos para um tipo.

Kaiepi
fonte