coreybrenner has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks, I'm looking to effect kind of a transparent nested active config system that stacks together as a configured object. Basically:
my $config = config( bool => true, token => 'a single string', value => [ 'a', 'list', 'of', 'strings' ], blah => store( # This function leaves a "constructor" in the c +onfig, that generates a new object when traversed. key => val, ... # Object typed and configured by these argume +nts... ), ... ); ... my $object = Type->new($config); # As Type builds an object, it refer +ences $config->value(), $config->token()... ... my $widget = $config->{path/to/some/nested/doohickey}; # This is actu +ally a nested tree traversal underneath... my $thingy = $widget->evaluate($with_this_context);
I'd like to make this configuration overlay as transparent as possible, allowing AUTOLOAD to catch calls to dereference names in this config. The problem is, because I have this set up as a tied hash, I have a few methods that get caught before AUTOLOAD can inspect the argument list and determine how to proceed, like:
sub FETCH { my $self = shift; if (! @_) { return $self->[0]->{FETCH}; } ... }
For methods like ->FETCH, some loophole in the context can be figured out, and I can do the right thing. I can later figure out a way to do:
$config->{FETCH} = SomeWidget->new(...); my $response = $config->FETCH->( term => [ 'range', 'of', 'values' ], ... );
But, this doesn't work for FIRSTKEY, DESTROY, etc. Is there any hole in the syntax that would allow me to detect a call to ->DESTROY that should release resources, versus a call which should search for a value in a hash and return it? Maybe a feature in the interpreter that would allow me to say:
sub FIRSTKEY { my $self = shift; if (! @_) { return $self->[0]->{FIRSTKEY}; } # ... walk the tree to gather nested keys $self->[1]->{iter} = \@list; # $_[0] is an empty array provided by the machinery that calls ->F +IRSTKEY underneath. my $name = shift(@list); push(@{$_[0]}, $name); # Either way works... return $name; }
And so on? This would allow me to basically trap the corner cases and provide the proper action to my constructed objects, no matter what key is being fetched via AUTOLOAD. Ultimately, the goal is an active configuration object able to formulate answers to questions (like $config->CFLAGS) on the fly, for a simple build system prototype. I'd just like to be able to not have to say:
# XXX: When dereferencing config keys, don't say ->FIRSTKEY, ->DESTROY +, ...

Replies are listed 'Best First'.
Re: AUTOLOAD vs. DESTROY...
by haukex (Archbishop) on Jan 08, 2023 at 10:27 UTC

    Having written a "do everything" object like this myself (WebPerl::JSObject), I can confirm that there are some methods you won't be able to override, and some you probably shouldn't (e.g. those from UNIVERSAL). IMHO, you probably should just bite the bullet and document for your users that not all method calls will be AUTOLOADed. Either that, or reconsider using AUTOLOAD in the first place - for example, I wrote IPC::Run3::Shell because the AUTOLOADing behavior of Shell is a bit too dangerous, or I wrote Util::H2O to provide protection against typos when accessing hashes. OTOH, for a dynamic object, a purely hashref- and arrayref-based interface might make sense from a user's point of view.

    However, methods like FETCH and FIRSTKEY shouldn't be a problem (WebPerl example, requires a modern browser). You haven't shown us an SSCCE, but I suspect that perhaps you're using a single class to handle the AUTOLOAD and the tied interfaces as well, which I would recommend against; you should use separate classes for each of the tied types.

    Also, a word of warning: it's possible to make an interface "too clever" such that it gets so complex that only the author will understand it ("It's so simple! Just press Ctrl+Shift+\ while clicking the middle mouse button!"). Consider as one counterexample the method-based API of Hash::Ordered, which some would consider that a quite clean API. Of course an API like the one you are designing could be made quite clean as well if you don't overload it too much, I don't know since you haven't described the whole thing, but it is something to keep in mind.

    Minor edits.

Re: AUTOLOAD vs. DESTROY...
by LanX (Saint) on Jan 07, 2023 at 22:32 UTC
    I'm not sure I understand your question completely, that's pretty abstract and terse.

    But as far as I can tell you are tieing an object reference to control it's behavior when used like a nested data structure.

    please note that you can also overload the dereferencing of the object's class for that effect.

    dereferencing => '${} @{} %{} &{} *{}',

    This should be cleaner and avoid any interference with tie's methods like FETCH, STORE, etc.

    HTH! :)

    Cheers Rolf
    (addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
    Wikisyntax for the Monastery