Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't my object attribute populated?

Tags:

raku

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.

like image 920
Christopher Bottoms Avatar asked Aug 14 '17 16:08

Christopher Bottoms


2 Answers

It is lazy, like many things in Perl 6. In other words, it intentionally doesn't waste time figuring out what the foo attribute is unless you ask for it. This is an optimization that avoids consuming computational resources unless you need them.

If you dump the data structure after calling the foo method, you'll see that it is populated in the data dump:

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 );

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

    dump $xml;
}
Foo is Bar
.RunInfo @0
├ $.foo = Bar.Str
├ $.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
like image 199
Christopher Bottoms Avatar answered Oct 22 '22 01:10

Christopher Bottoms


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.

like image 36
smls Avatar answered Oct 22 '22 00:10

smls