Initialization Impasse

[email protected]
Newsgroups perl.moose
Message-ID <[email protected]>
Hi, all,

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!

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.

All my audio file classes have a tag() and a uid() method. Tag() writes or
reads and returns a hash with the (normalized) tag/comment keys and values.
uid() writes or reads and returns a unique id associated with the audio
file. Some audio formats (like Ogg Vorbis) don't have a native uid field,
so it has to be stored in a tag. I decided that the uid is important enough
to merit its own attribute, and ran into some initialization issues that
make the code less clean than I'd like:

  - 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?

  - 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?

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

=====================================================================
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.