Is Moose obliged to call builder again after call to clearer?
Asked Answered
H

1

6

I want to fetch elements from a list that is stored inside a Moose class. The class knows how to populate that list itself. It's sort of like an iterator, except that I want to be able to reset the iterator, and start fetching the same elements from that list as if I hadn't already done so. I intend to call as follows:

while( my $slotlist = $class->get_next_slotlist ) {
    # do something with $slotlist
}

Except that, as I said, I may want to reiterate over the same elements again at a later point:

$class->reset_slotlists;
while( my $slotlist = $class->get_next_slotlist ) {
    # do something with $slotlist
}

I thought of designing the class along the lines of the following stripped-down (mock) version:

package List;
use Moose;

has '_data' => (
    traits    => [ 'Array' ],
    is        => 'ro',
    predicate => 'has_data',
    lazy      => 1,
    builder   => '_build_data',
    clearer   => 'reset',
    handles   => {
        next => 'shift',
    },
);

sub _build_data { [ 'abc', 'def' ] }

package main;
use strict;
use warnings;
use Test::More;
use Test::Pretty;

my $list = List->new;
ok ! $list->has_data;
is $list->next, 'abc';
is $list->next, 'def';
is $list->next, undef;
is $list->next, undef;
$list->reset;
is $list->next, 'abc', 'Moose calls builder again after clearing';
is $list->next, 'def';
is $list->next, undef;
is $list->next, undef;
ok $list->has_data;

done_testing;

When I run this, Moose calls the builder again after the call to reset() (i.e., the clearer). My question is now: is this behaviour guaranteed? The documentation doesn't say.

(While typing this question, I also started wondering: do you think this is bad design? I like the iterator-like interface, very elegant on the calling side, and easy to implement. But is this very question a sign that the design isn't good?)

Haugh answered 12/2, 2014 at 12:57 Comment(1)
You should probably voice that in #moose on irc.perl.org. The people responsible for Moose will know best.Friesen
D
8

Yes, if a lazy attribute has been cleared, then the next time an accessor is called, the value will be rebuilt.

Clearing _data is essentially like doing delete($self->{_data}). Or it would be if Moose objects were hashrefs, but they're not hashrefs, they're objects. (Actually they are hashrefs underneath but part of the Moose experience is that we're supposed to pretend we don't know that. Winking face.)

Lazy attributes use exists($self->{_data}) in order to decide whether the value needs to be built.

I don't think it's a bad design, but if the array is big, then keeping a copy in _data to iteratively destroy with shift is potentially wasting memory. You could just keep a counter of where you are in the array and increment it each time.

Update: You are correct that this is not documented very well.

Despotic answered 12/2, 2014 at 13:50 Comment(1)
The operative word is "lazy". The builder is not called after a clear if the lazy attribute is not set. That detail is easy to miss.Perichondrium

© 2022 - 2024 — McMap. All rights reserved.