Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Re: RFC - Parameter Objects

by hossman (Prior)
on May 14, 2003 at 18:16 UTC ( [id://258190]=note: print w/replies, xml ) Need Help??


in reply to RFC - Parameter Objects

three minor comments...
  • Why make the mutators "set_foo" ... why not just overload "foo" (ie: foo with args does a set, and returns true if-and-only-if the args are valid, without args returns whatever the parameter value(s) should be.
  • Frequently when I've needed to use Parameter Objects (or "Keys" as their sometimes called) there are acctually two issues of validity:
    1. Are the individual parameters valid?
    2. is the combination of parameters valid?
    ... you've addressed the first, but not the second. There are lots of examples of when this becomes crucial, but the the most basic is: what if none of the set methods are called?" Typically the easiest solution is to have a unified "isValid" method on all of your Parameter Objects that returns true if-and-only-if all of the required parameters have been set, and there is "internal consistency" among the parameters, then any method that wants to take in a Parameter object doesn't need to do it's own validation, it can just call $param->isValid() However ... in cases where i've seen this done, it works becaue the methods expect a particular sub class of the "Parameter Object" class, and each sub class defines it's own isValid method. Based on your design, I'm not really sure what the best way to do this would be (because whoever calls "new Sub::ParamObject" can pass whatever rules they want.
  • Your example validation for month has a slight bug in it...
    my %params = ( type => qr/Order|Return/, month => sub { my $month = shift; return grep { /$month/ } (1 .. 1 +2) } );
    ..that would allow me to pass '' as a valid month.

Replies are listed 'Best First'.
Re: Re: RFC - Parameter Objects
by Ovid (Cardinal) on May 14, 2003 at 18:58 UTC

    I have to admit that I've never cared for overloaded methods that function as both accessors and mutators, but in this case, it might make sense.

    The combination of parameters can be dealt with if I pass $self to the subroutines used for validation. Then, each item can check the other values, but this could make things order dependant (untested).

    my $param = Sub::ParamObject->new({ foo => qr/\d+/, bar => sub { $_[0]->{foo} > 3 && $_[1] =~ /^this|that$/ } }); $param ->foo(7) ->bar('this'); # succeeds $param ->foo(2) ->bar('this'); # fails

    And thanks for the bug catch!

    Cheers,
    Ovid

    New address of my CGI Course.
    Silence is Evil (feel free to copy and distribute widely - note copyright text)

      While this is nice and convenient for those cases where you have independently valid parameters, it's impossible (or dangerous, you choose) to have pairs (or more) of parameters that are valid together :

      my $ratio = Sub::ParamObject->new({ nominator => qr/^[-+]?\d+$/, denominator => sub { $_[1] =~ qr/^\d+$/ and $_[1] != 0 }, }); # works and is convenient $ratio->nominator(-1); $ratio->denominator(1); my $source = Sub::ParamObject->new({ selector => qr/^filename|url$/, filename => sub {$_->[0]->{selector} eq 'filename' and $_[1] =~ +qr/^\w+$/ }, url => $_->[0]->{selector} eq 'filename' and $_[1] =~ qr!^h +ttps?://!i }, }); # and now ??? # I want to have selector set to one of the values # and (only) the corresponding field set. $source->selector('filename')->filename('foo.txt'); # works $source->filename('foo.txt')->selector('filename'); # cannot in my i +mplementation

      Either you make the approach dependent on the order of parameters (plausible but ugly IMO), or you find another approach to validation (which I would rather welcome, since I did stuff like this years ago and didn't find a good solution to this).

      perl -MHTTP::Daemon -MHTTP::Response -MLWP::Simple -e ' ; # The $d = new HTTP::Daemon and fork and getprint $d->url and exit;#spider ($c = $d->accept())->get_request(); $c->send_response( new #in the HTTP::Response(200,$_,$_,qq(Just another Perl hacker\n))); ' # web

        Making the approach dependent on the order in which the mutators is called would be ugly. It would also be bug prone. Forget it just once and you might be wondering why the heck your code is misbehaving.

        Perhaps the contructor could be modified. The current hashref would fall under a key named "params" and a different key named "groups" would list group validations. Then, as soon as an accessor is called, the "group" validations would be checked. This would eliminate the order dependence. Workable?

        Cheers,
        Ovid

        New address of my CGI Course.
        Silence is Evil (feel free to copy and distribute widely - note copyright text)

        The problem here is that your validation code assumes a certain order.
        my $source = Sub::ParamObject->new({ selector => sub { my $self = shift; return unless $_[0] =~ /^(filename|url)$/; return if @$self{qw(filename url)} and !exists $self->{$1}; }, filename => sub { my $self = shift; return (!exists $self->{selector} or $self->{selector} eq 'filename +') and $_[1] =~ qr/^\w+$/; }, url => sub { my $self = shift; return (!exists $self->{selector} or $self->{selector} eq 'url') and $_[1] =~ qr!^https?://!i }, });
        If you squint a bit you'll notice two levels of validation: one is of the value as such, and one is of the dependencies. This can be condensed and clarified by allowing multiple tests:
        my $source = Sub::ParamObject->new({ selector => [ qr/^(filename|url)$/, sub { @{$_[0]}{qw(filename url +)} and !exists $self->{$_[1]} }, ], filename => [ qr/^\w+$/, sub { !exists $_[0]->{filename} or $_[0]- +>{selector} eq 'url' }, ], url => [ qr!^https?://!i, sub { !exists $_[0]->{filename} or $_[0] +->{selector} eq 'url' }, ], });
        Getting the order right however is bound to get tricky for large parameter sets. That's a problem which can only be expressed well with the semantics of a logical programming language such as Prolog or make. The fundamental obstacle with order-dependent validation is that you need to change the validity of things based on that of others. You might try something like this:
        my $source = Sub::ParamObject->new(test => { selector => { value => qr/^(filename|url)$/, }, filename => { value => qr/^\w+$/, }, url => { value => qr!^https?://!i, }, }, if_valid => { selector => sub { $_[0]->add_check($_[1] eq 'url' ? 'filename' : 'url', forbidde +n => sub { return }); }, filename => sub { $_[0]->add_check(selector => need_filename => sub { $_[1] ne ' +url' }); }, url => sub { $_[0]->add_check(selector => need_url => sub { $_[1] ne 'filen +ame' }); }, });
        I used a hash of hashes here so checks can also easily be removed by a ->remove_check(). Even this is likely to become a maze of little tests, all alike, for large parameter sets, though.

        Makeshifts last the longest.

      That approach wouldn't solve the main case I pointed out where it becomes important to have validation...
      what if none of the set methods are called?
      ...ie: what if someone constructs a Params object, but never sets any values in it? Then there is no validation of any kind.

        You are right, but I think it might be solved. In a response to Corion, I wrote:

        Perhaps the contructor could be modified. The current hashref would fall under a key named "params" and a different key named "groups" would list group validations. Then, as soon as an accessor is called, the "group" validations would be checked. This would eliminate the order dependence. Workable?

        When an accessor is called, the parameters are validated. If a value is never set, we can still validate it when the accessor is called. This is still not foolproof, but I think it's a reasonable scheme. Let me know if you think there are problems with this.

        Cheers,
        Ovid

        New address of my CGI Course.
        Silence is Evil (feel free to copy and distribute widely - note copyright text)

Re: Re: RFC - Parameter Objects
by rcaputo (Chaplain) on May 29, 2003 at 17:19 UTC

    hossman wrote:

    Why make the mutators "set_foo" ... why not just overload "foo" (ie: foo with args does a set, and returns true if-and-only-if the args are valid, without args returns whatever the parameter value(s) should be.

    Sorry to come into this late, but I didn't feel the question was answered. There are a couple related reasons not to overload mutators or accessors.

    Most of the time, coders are aware whether they intend to call a mutator in "set" or "get" mode. This awareness happens at coding time.

    Having separate mutators for "set" and "get" allows code to be explicit about its intentions. Mutators may be prototyped such that accidentally supplying or omitting a parameter is a compile-time error rather than a runtime misbehavior. If prototypes aren't desirable, separate mutators still makes mismatches between design and code more visible.

    To illustrate that last point, compare misused overloaded mutators with misused explicit ones.

    $thing->foo(); $foo = $thing->foo($bar);

    vs.

    $thing->get_foo(); $foo = $thing->set_foo($bar);

    The latter form of each example is legal code, and only the surrounding context will determine whether they are errors.

    An obvious solution is to check the number of parameters given against the mutator's runtime context. The resulting code might look like this.

    sub foo { my $self = shift; if (@_) { if (defined wantarray) { croak "can't expect a return value when setting foo"; } # store foo = shift; } else { unless (defined wantarray) { croak "must expect a return value when fetching foo"; } # return foo member here } }

    That has its own drawbacks.

    "set" mutators cannot be chained. While foo() might return $self in "set" mode, the wantarray() check would make it an error to actually use it.

    "set" mutators cannot be allowed to return their members' new values. Even if they did, the wantarray() checks would prohibit code like print $thing->foo($new_value);

    It's still a runtime error. Ideally every line of a program should be instrumented before it goes into production. Pragmatically speaking, compile time errors are better at preventing bogus code from being released.

    Even if these problems are not an issue for a particular application, every mutator has gained an enormous amount of avoidable runtime overhead.

    -- Rocco Caputo - troc@pobox.com - poe.perl.org

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others rifling through the Monastery: (2)
As of 2024-06-22 09:35 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?
    erzuuli‥ 🛈The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.