Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Inheritable configuration options.... but with default values?

by Amblikai (Scribe)
on Feb 20, 2020 at 16:57 UTC ( [id://11113260]=perlquestion: print w/replies, xml ) Need Help??

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

Hi Monks!

I'm racking my brain with this one and can't seem to find an elegant solution. I hope you can help!

Basically i have a series of classes and subclasses, each level adding a bit more functionality to its parent and in each class i have behaviour which can be modified by configuration options which are specific to that class

All pretty straight forward so far. I can have a class method which sets the values of a hash which can be accessed by each object.

However, how do i have default values for my configuration options (written into the class)? Which can be added to in each subsequent class?

For example (Pseudo Code):

package base; my %cfg=( on_error => "exit", output_type => "text", ); sub new { ..blah blah.. do_something if ($cfg{on_error} eq 'exit'); } package child; use parent "base"; my %cfg=( on_error => "warn", ); sub new {...}

Where in the above example, the child class has the default value for "output_type" inherited from the parent class, but can change configuration options too

I've set off in the direction of having a config class singleton which is instantiated (referenced) from within the child classes but i can't fathom how to give it default values in the manner above

Thanks in advance for any help!

Replies are listed 'Best First'.
Re: Inheritable configuration options.... but with default values? (updated)
by LanX (Saint) on Feb 20, 2020 at 17:16 UTC
    I can spot at least two issues:

    • You can't inherit private (sic) vars, swap my with our for package vars in the base definition
    • you are doing a re-init which will overwrite old config

    you most likely want either

    local $cfg{on_error} = "warn";

    or

    my %cfg=( # now my to avoid overwriting any glob +al %cfg %cfg, # inherited defaults * on_error => "warn", );

    (untested)

    edit

    and in the second case you won't even need inheritance because with pkg-vars you can be explicit

    my %cfg=( # now my to avoid overwriting any glob +al %cfg %base::cfg, # inherited defaults * on_error => "warn", );

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

    update

    added local and my

    update

    There are more options ... but the expected behavior is not clear to me ...

    To answer this properly we'd need to see an SSCCE

    update

    *) fixed error in order, swl++

      This order of hash entries will mean the global values override the local.

      It should be:

      my %cfg=( %base::cfg, on_error => "warn", );
        Of course you are totally right.

        I was probably too occupied puzzling about the intended semantics.

        Fixed!

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

Re: Inheritable configuration options.... but with default values?
by haukex (Archbishop) on Feb 20, 2020 at 17:58 UTC

    To make full (and "correct") use of OO, the properties need to be encapsulated.

    use warnings; use strict; package CfgBase { use Moo; use namespace::clean; has on_error => (is => 'ro', default => "exit"); has output_type => (is => 'ro', default => "text"); sub dump { my $self = shift; print ref($self),":\n"; print " $_: ",$self->$_,"\n" for qw/ on_error output_type /; } } package CfgChild { use Moo; use namespace::clean; extends 'CfgBase'; has '+on_error' => (default => "warn"); } CfgBase->new->dump; CfgChild->new->dump;
Re: Inheritable configuration options.... but with default values?
by 1nickt (Canon) on Feb 20, 2020 at 17:58 UTC

    Hi, I would use Moo for all your object creation needs if I were you.

    package MyBase { use Moo; has cfg => ( is => 'rw', default => sub { return { on_error => 'exit', output_type => 'text' }; }, ); }; package MyChild { use Moo; extends 'MyBase'; sub BUILD { shift->cfg->{on_error} = 'warn'; } }; use strict; use warnings; use MyChild; use Test::More; ok( my $obj = MyChild->new ); is_deeply( $obj->cfg, { on_error => 'warn', output_type => 'text' } ); done_testing;

    Hope this helps!


    The way forward always starts with a minimal test.
Re: Inheritable configuration options.... but with default values?
by bliako (Monsignor) on Feb 20, 2020 at 22:09 UTC

    Fellow monks showed you how to do it with Moo. I will show you how to do it without any OO system.

    # Simple OO example with inheritance # author: bliako # for https://perlmonks.org/?node_id=11113260 # 02-20-2020 package Class1 { use warnings; use strict; sub new { my ($class, $config) = @_; print "Class1 called.\n"; my $self = { # set an error handler to be used by error(). # NOTE: anonymous subs are not member subs, so you +can't access $self... on_error => sub { print "on_error() : exiting with msg '$_[ +0]'\n"; exit(1); }, name => "<no-name-set>", }; bless $self => $class; # my idiomatic way to remember bless or +der, i.e. bless $self, $class $self->_init_from_config($config); return $self; } sub _init_from_config { my $self = shift; my $config = shift; # a hashref of config params return unless defined $config; for (keys %$config){ $self->{$_} = $config->{$_}, print "config: set '".$_."'.\ +n" if exists $self->{$_} } } sub myname { return $_[0]->{name} } sub error { my $self = shift; print "error() : called with $self, my name is ".$self->myname +()."\n"; $self->{on_error}->(@_); } } package Class2 { use warnings; use strict; use parent -norequire, 'Class1'; sub new { my ($class,$config) = @_; print "Class2 called.\n"; my $self = $class->SUPER::new($config); $self->{name} = "my name is Class2"; return $self; } } package Class3 { use warnings; use strict; use parent -norequire, 'Class2'; sub new { my ($class,$config) = @_; print "Class3 called.\n"; my $self = $class->SUPER::new($config); $self->{on_error} = sub { print "on_error() : warning with msg '$_[0]'\n"; }; return $self; } } package main; use warnings; use strict; use Data::Dumper; my $c2 = Class2->new(); my $c3 = Class3->new({ name => 'my name is Jack' }); print "here is c2:\n".Dumper($c2); $c3->error("oops for c3"); $c2->error("oops for c2"); print "you will not see this (because c2's error handler inherits c1's + which will exit()).\n";

    bw, bliako

      I'm confused, in my book is new() reserved for object constructors not classes.

      Inheritance for classes should be easier done by

      use strict; use warnings; use Data::Dump qw/pp dd/; package Class1 { our %cfg = (a => 1, b=>2); sub cfg { %cfg } }; package Class2 { use parent -norequire, 'Class1'; our %cfg = ( %Class1::cfg, a=>11,c=>33 ); }; package Class3 { use parent -norequire, 'Class2'; our %cfg = ( %Class2::cfg, b=> 222 ); }; pp \%Class1::cfg, \%Class2::cfg, \%Class3::cfg; # **** THIS DOESN'T WORK package Class2b { use parent -norequire, 'Class1'; our %cfg = ( SUPER->cfg(), a=>11,c=>33 ); }; pp {Class1->cfg()}, \%Class2b::cfg;

      Please note that I couldn't make it work with SUPER, ( but I'm no SUPER expert anyway ;-)

      (
        { a => 1, b => 2 },
        { a => 11, b => 2, c => 33 },
        { a => 11, b => 222, c => 33 },
      )
      Can't locate object method "cfg" via package "SUPER" (perhaps you forgot to load "SUPER"?) at d:/tmp/pm/class_cfg.pl line 32.
      
      Compilation exited abnormally with code 255 at Fri Feb 21 00:04:51
      

      edit

      ah I misread perlobj

      > The SUPER modifier can only be used for method calls. You can't use it for regular subroutine calls or class methods:

      update

      This works, albeit with ugly syntax.

      ... package Class2b { use parent -norequire, 'Class1'; our %cfg = ( __PACKAGE__->SUPER::cfg(), a=>11,c=>33,n=>'2b' ); }; pp \%Class2b::cfg;

      { a => 11, b => 2, c => 33, n => "2b" }
      

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        You are using package variables (what other languages call static) and in your update you have the right syntax. If you had an object, the way to access its parent's package variable is stated in perlobj, at the place you quoted it: for a static method: $self->SUPER::save(); and for a package variable %{$self->SUPER::cfg};

        But package variables are not "object variables" or member variables (?whatever?). It would be better to store %cfg into $self's hash for at least one reason: inheritance is taken care by Perl for free. Whereas in your case, Perl takes care of the inheritance of Class1 into Class2 and YOU MUST (not forget to) take care of the inheritance of Class1's package variables. That's a lot of boiler work (for me ;) ).

        related: https://stackoverflow.com/questions/3109672/how-to-make-a-hash-available-in-another-module and Perl Inheritance & module variables . The latter in particular is similar to what you have shown.

        TIMTOWTDI

        use strict; use warnings; use Data::Dump qw/pp dd/; package Class1 { use constant cfg => { a => 1 }; }; package Class2 { use parent -norequire, 'Class1'; use constant cfg => { %{__PACKAGE__->SUPER::cfg}, b => 2 }; }; package Class3 { use parent -norequire, 'Class2'; use constant cfg => { %{__PACKAGE__->SUPER::cfg}, c => 3 }; sub meth { warn "a is ",cfg->{a} } }; pp Class1::cfg, Class2::cfg, Class3::cfg ; Class3->meth();
        ({ a => 1 }, { a => 1, b => 2 }, { a => 1, b => 2, c => 3 })
        a is 1 at d:/tmp/pm/class_cfg.pl line 18.
        

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11113260]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (4)
As of 2024-03-28 17:13 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found