¿Cómo puede generar dinámicamente valores para usar con rasgos?

8

Para una biblioteca que estoy escribiendo, tengo un atributo en un CÓMO que usa el handlesrasgo para delegar métodos de varios roles realizados por otro CÓMO que usa a una instancia de ese CÓMO. Mi primer intento se veía así (aunque para hacer esto más fácil de leer, esto solo se resolverá 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

El problema con esto es que si alguno de los tipos que hago de este CÓMO maneja métodos para cambios, esto ya no funcionará con todos sus métodos. Entonces, en cambio, quiero generar dinámicamente una lista de métodos que deben manejarse dada una lista de métodos que este CÓMO anula y una lista de tipos cuyos métodos deberían manejarse. Esto no es tan fácil como parece debido a cómo handlesse implementa el rasgo. Por ejemplo, esto no 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 ∌ *)
};

Entonces, ¿cómo generaría dinámicamente un valor para que un rasgo lo use en casos como este?

Kaiepi
fuente

Respuestas:

7

La aplicación de rasgo se configura durante la compilación, por lo que también necesitamos una forma de generar un valor para que la use durante la compilación. Esto se puede hacer usando el BEGINphaser, pero esto se escribe mejor constanten este caso.

Las constantes en Raku no son solo variables a las que no puede asignar o vincular después de declararlas. Normalmente, cuando declara una variable, su símbolo se instala durante la compilación, pero su valor no se establece realmente hasta el tiempo de ejecución, por lo que esto puede suceder:

my Int:D $foo = 1;

BEGIN say $foo; # OUTPUT: (Int)

Este no es el caso con constant; el compilador establece el valor del símbolo durante la compilación. Esto significa que para el ejemplo en la pregunta, podemos generar dinámicamente una lista de métodos para handlesusar de esta manera:

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;

Si por alguna razón los símbolos como PARENT_METHOD_OVERRIDESy PARENT_METHODSno existen en el contexto del tipo, aún puede manejar los rasgos de esta manera declarando las constantes y agregando los atributos desde dentro de un cierre; Las declaraciones de método y atributo tienen un alcance de tal manera que puede escribirlas desde cualquier lugar dentro del paquete del tipo y aún así agregarlas al tipo. Tenga en cuenta que los métodos y atributos se manejan durante la compilación, por lo que no es así como manejaría algo como generar dinámicamente atributos o métodos para un tipo.

Kaiepi
fuente