Re: Initialization Impasse
[email protected] (Stevan Little)
| Newsgroups | perl.moose |
|---|---|
| Message-ID | <[email protected]> |
Bernardo, On May 8, 2008, at 1:12 PM, [email protected] wrote: > I've been playing with Moose in the last few months, writing an > audio file > management suite of modules, and I am in awe of and in love with > it. It's > one of the best things that have happened to Perl in the last few > years. > Congratulations and thanks to all involved in its creation! Thanks :) > I have a couple of questions, which I'll try to put to you in separate > posts, so as not make things too confusing. Here is the first one. > > - uid() tickles the (lazy) tag attribute every time it's called, > when it's > only needed the first time. I could forego the tag attribute's > lazyness, > but that can be wasteful too, in case the uid and the tag are not used > (initializing the tag requires the creation of a taglib file > object, and > then calling a method, and some other processing). I think what I > need here > is a way to test (in before_uid() ) whether the tag has already been > initialized, in which case I can return without doing anything). Is > there a > way to test for initializedness of an attribute? Yes, take a look at the predicate option, it is currently only documented in Class::MOP::Attribute, but you use it like this: has 'foo' => (is => 'rw', predicate => 'has_foo'); If foo has never been set, then $obj->has_foo will return false, if foo has been set (even with undef) then it will return true. > - To work around infinite recursion (if uid() called tag(), which > calls > uid() ), I set the uid value directly, but that assumes a hash-based > object. Is there a cleaner or moosier way of setting the uid > attribute? Hmm, Moose can't really help this. I suggest breaking the cycle at some point, or if you cant, looking into a proxy object I wrote one for Bread::Board recently (http://search.cpan.org/~stevan/Bread- Board-0.03/lib/Bread/Board/Service/Deferred.pm). - Stevan > This may well be an XY problem, where I think I need something I don't > really need, so I'm attaching some pruned code below for more details. > > Bernardo > > > ===================================================================== > > package Audiotheque::AudioFile; > use Moose; > use MooseX::Types::Path::Class qw(File); > with 'Audiotheque::Tag'; > with 'Audiotheque::Transcode'; > > has 'path' => ( is => 'ro', isa => File, coerce => 1, required => 1, > writer => '_set_path' ); > > has 'uid' => ( is => 'rw', isa => 'Maybe[Str]', lazy => 1, > default => undef ); > > sub save_tag { } # Required by Audiotheque::Tag > > ===================================================================== > > package Audiotheque::AudioFile::Ogg::Vorbis; > use Moose; > use Moose::Autobox; > use MooseX::ClassAttribute; > extends 'Audiotheque::AudioFile'; > use Audio::TagLib::Ogg::Vorbis::File; > > has '+tag' => ( > default => sub { > my ($self) = @_; > > # _normalFields() comes from Audiotheque::Tag role > my $tag = $self->_normalFields($self->_taglibTag); > $self->_move_uidFromTag($tag); > > return $tag; > }, > ); > > # Insist on moving UID to its own attribute, if it appears in a tag > argument > after 'tag' => sub { > my ($self, $tag) = @_; > > $self->_move_uidFromTag($tag) if defined $tag; # Only on write > }; > > # Vorbis doesn't have a built-in UID, like the MD5 signature of > FLAC, so > # it has to store it in a Vorbis comment. Because the uid has a > special > # status, it's given its own accessor (read-only), and shouldn't be > # accessible from the (more generic) tag method. Too many gates to > # guard. To this end, if a UID is found in the tags (which come > from the > # Vorbis comments), it is copied to the uid attribute and deleted from > # the tags. > # > sub _move_uidFromTag { > my ($self, $tag) = @_; > > if ( $tag->exists('UID') ) { > if ( $tag->at('UID')->[0]->defined ) { > # FIXME assumes hash-based obj. Direct access is to avoid > # infinite recursion when tickling tag from uid. > $self->{uid} = $tag->at('UID')->[0]; > } > $tag->delete('UID'); > } > } > > #has '+uid' => (builder => '_build_uid'); > # > #sub _build_uid { } > # > #after '_build_uid' => sub { $_[0]->tag } > > # Give a tickle to the (lazy) tag attribute, in case it hasn't been > set yet. > # FIXME This happens on every uid access, but it's really needed > only for > # the very first one. Quite a waste of sub calls. Is there a way > # to test for "initializedness" (as opposed to definedness)? > before 'uid' => sub { > my ($self, $uid) = @_; > > $_[0]->tag if not defined $uid; # Tickle only on attribute read. > }; > > has '_taglibFile' => ( > isa => 'Maybe[Audio::TagLib::File]', > lazy => 1, > default => sub { Audio::TagLib::Ogg::Vorbis::File->new( > $_[0]->path ) }, > accessor => '_taglibFile', > clearer => '_clear_taglibFile', > predicate => '_has_taglibFile', > handles => { save => 'save' }, > ); > > # Required by Audiotheqhe::AudioFile class <- Audiotheque::Tag role > override 'save_tag' => sub { > my $self = shift; > [update taglibFile comments from tag] > $self->save; > }; > > sub _taglibTag { > my($self) = @_; > my %tag = [get comments from _taglibFile] > return \%tag; > } > > ===================================================================== >