Search code examples
raku

Why isn't my object attribute populated?


Given this oversimplified XML file:

<Foo>Bar</Foo>

And this code which extracts the value for the Foo element:

use XML::Rabbit;
use Data::Dump::Tree;

class RunInfo does XML::Rabbit::Node {
    has $.foo is xpath("/Foo");
}

sub MAIN ( $file! ) {

    my $xml = RunInfo.new( file => $file );

    dump $xml;

    put "-----------------------";
    put "Foo is $xml.foo()";
}

You'll see that the value for foo is Nil, even though the output shows Foo is Bar:

.RunInfo @0
├ $.foo = Nil
├ $.context is rw = .XML::Document @1
│ ├ $.version = 1.0.Str
│ ├ $.encoding = Nil
│ ├ %.doctype = {0} @2
│ ├ $.root = .XML::Element @3
│ │ ├ $.name is rw = Foo.Str
│ │ ├ @.nodes is rw = [1] @4
│ │ │ └ 0 = .XML::Text @5
│ │ │   ├ $.text = Bar.Str
│ │ │   └ $.parent is rw = .XML::Element §3
│ │ ├ %.attribs is rw = {0} @7
│ │ ├ $.idattr is rw = id.Str
│ │ └ $.parent is rw = .XML::Document §1
│ ├ $.filename = example.xml.Str
│ └ $.parent is rw = Nil
└ $.xpath is rw = .XML::XPath @9
  ├ $.document = .XML::Document §1
  └ %.registered-namespaces is rw = {0} @11
-----------------------
Foo is Bar

(Disclaimer: I came across this behavior today in my code, so I wrote it up Q & A style. Other answers welcome.).

By the way, here are links to XML::Rabbit and Data::Dump::Tree.


Solution

  • This is not the result of a built-in Perl 6 feature, but rather something the XML::Rabbit module does.

    That module provides the is xpath trait, and makes sure that at class composition time, any attribute which has that trait applied gets its accessor method overridden with a custom one.

    The custom accessor method calculates and sets the value for the attribute the first time it is called, and on subsequent calls simply returns the value that's now already stored in the attribute.

    The custom accessor method is implemented as follows (taken from the module's source code with parts elided):

    method (Mu:D:) {
        my $val = $attr.get_value( self );
        unless $val.defined {
            ...
            $val = ...;
            ...
            $attr.set_value( self, $val );
        }
        return $val;
    }
    

    Here, $attr is the Attribute object corresponding to the attribute, and was retrieved prior to installing the method using the Meta-Object Protocol (MOP).


    The Data::Dump::Tree module, in turn, doesn't use the accessor method to fetch the attributes's value, but rather reads it directly using the MOP.

    Therefore it sees the attribute's value as Nil if has not yet been set because the accessor was not yet called.