Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add an attribute to an object using the meta-object protocol?

Tags:

class

raku

I was trying to answer this question, and thought I could use the meta-object protocol to add attributes to a class. Here is a minimal example where I try to add an attribute test to the class Configuration after construction:

use v6;

class Configuration {
}

my $config = Configuration.new;
my $attr = Attribute.new(
    :name('$.test'), # Trying to add a "test" attribute
    :type(Str),
    :has_accessor(1), 
    :package(Configuration)
);
$config.^add_attribute( $attr );
$config.^compose();
say "Current attributes: ", join ', ', $config.^attributes();
$attr.set_value( $config, "Hello" ); # <-- This fails with no such attribute '$.test'
say $config.test;

When I run this, I get:

Current attributes: $.test
P6opaque: no such attribute '$.test' on type Configuration in a Configuration when trying to bind a value
  in block <unit> at ./p.p6 line 16
like image 967
Håkon Hægland Avatar asked Aug 19 '18 17:08

Håkon Hægland


1 Answers

Attributes cannot be added after class composition time, which occurs at compile time when the closing } is reached when compiling the program. (This is the case for the P6opaque representation. It's not impossible that a representation could exist that allows this, but there's none specified at this time.)

Further to that, .^add_attribute is called on the meta-object, and for a class the attributes are per type, not per object; the code structure suggests that perhaps the expectation was per object. There's nothing that makes it impossible to have prototype object orientation (actually the MOP is designed so somebody could implement such an object system in Perl 6), but again there's nothing specified in Perl 6 itself that provides this.

Thus with the provided object system, such manipulation needs to be done at compile time, and before the closing }. That can be achieved as follows:

class Configuration {
    BEGIN {
        my $attr = Attribute.new(
            :name('$!test'), # Trying to add a "test" attribute
            :type(Str),
            :has_accessor(1),
            :package(Configuration)
        );
        Configuration.^add_attribute( $attr );
    }
}

my $config = Configuration.new;
say "Current attributes: ", join ', ', $config.^attributes();
$config.^attributes[0].set_value( $config, "Hello" );
say $config.test;

This is one of the many places where Perl 6 is dynamic primarily by inviting the programmer to participate in compile time, rather than by making all things possible at runtime.

Finally, I'll note that there is a means to add attributes to an existing object, and on a per-object basis: by using does to mix a role in to it. That works by changing the type of the object along the way. There's some documentation on does here.

like image 97
Jonathan Worthington Avatar answered Oct 26 '22 08:10

Jonathan Worthington