Re: RFC - Parameter Objects
by fruiture (Curate) on May 14, 2003 at 18:08 UTC
|
Being into OO in general, i'd say a certain collection of data that is used more than once should be made an object anyway, so i'd create a class supporting only accessors and mutators and no further methods. That's already close to your idea, only that each object is it's own class. That's where Class::Object and Class::Classless cross my thoughts, but these are too general for Paramtereobjects. So I'd suppose not Sub::ParamObject but Class::(Object|Classless)::ParamObject as name, although it might make people think it's built on top of one of the two. It could be, somehow.
--
http://fruiture.de
| [reply] |
|
Each in its own class? Aack! Why didn't I see that bug coming? If more than one parameter object is used, identically named parameters would conflict. To get around this, the simplest thing that could possibly work could be for me to just auto-increment the class name of the parameter object. However, building this on top of Class::Object does look like a nice way to go. However, I might have a problem with the resulting object already having new() and sub() methods.
Cheers,
Ovid
New address of my CGI Course.
Silence is Evil (feel free to copy and distribute widely - note copyright text)
| [reply] |
|
Yep, a counter is also Class::Object's method to create unique classnames. As for the new() and sub() methods: I don't think they'd conflict with the purpose of the parameter object, even more they are very usefull for introducing hossman's validation methods (why limit this to one isValid, why not isValidForPurposeA and isValidForPurposeB ...) and to derive objects from objects. To me, it's just fulfillment of concept ;)
--
http://fruiture.de
| [reply] |
Re: RFC - Parameter Objects
by hossman (Prior) on May 14, 2003 at 18:16 UTC
|
| [reply] [d/l] [select] |
|
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) | [reply] [d/l] |
|
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
| [reply] [d/l] [select] |
|
|
|
|
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.
| [reply] |
|
|
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
| [reply] [d/l] [select] |
Re: RFC - Parameter Objects
by chromatic (Archbishop) on May 14, 2003 at 18:29 UTC
|
Interesting. I've often thought that one failing of type checking parameter lists is that they can only tell you if a parameter fits in a type hierarchy somehow, not if the value of the parameter is actually valid.
| [reply] |
|
I actually got that idea from my CSV Database Validation program. For that, you can specify a regular expression as a "data type" for a database field. In thinking about this more, I realized that by arbitrarily passing in subroutines to validation routines, rather than try to hard-code methods that cover every possible case, I can let the programmer specify those in advance via sub references. It seems like a much more powerful system, the code is shorter, and you only build what you need.
I do agree with you about the failings of parameter lists. Design by Contract seems to be one attempt to get around this weakness.
Cheers,
Ovid
New address of my CGI Course.
Silence is Evil (feel free to copy and distribute widely - note copyright text)
| [reply] |
Re: RFC - Parameter Objects
by Juerd (Abbot) on May 15, 2003 at 06:50 UTC
|
use Attribute::Property;
{
package Param::function;
sub new : New;
# XXX - Both need anchors?
sub type : Property { /Order|Return/ }
sub month : Property { grep /$_[1]/, 1 .. 12 }
}
sub function {
my $self = shift;
...
}
After which you can do
my %params = (
type => qr/Order|Return/,
month => sub { my $month = shift; return grep { /$month/ } (1 .. 1
+2) }
);
my $param = Param::function->new(\%params);
$param->type = 'Return';
$param->month = 3;
print $param->type; # prints 'Return'
print $param->month; # prints 3
# the following two method calls will fail (croaking)
$param->type = 'Ovid';
$param->month = 13;
But I dislike param objects. The idea is great, but it's too much work in practice. Not because I created many classes, but because it's just too much work to create objects every time you want to pass parameters. I ended up using my $param = @_ == 1 ? shift : Param::function->new({ @_ });
everywhere and not actually using the parameter classes.
Juerd
# { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }
| [reply] [d/l] [select] |
|
Juerd wrote:
But I dislike param objects. The idea is great, but it's too much work in practice. Not because I created many classes, but because it's just too much work to create objects every time you want to pass parameters.
That just means I need to update the POD to make it more clear what these objects are for. Thanks!
Parameter objects aren't for every subroutine or method. Instead, imagine the following code:
sub foo {
my ($quantity, $mangled, $item, $color, $puppies) = @_;
...
}
sub bar {
my ($thing, $quantity, $color, $item) = @_;
...
}
sub baz {
my ($quantity, $mushroom, $color, $item, $limit) = @_;
...
}
sub quux {
my ($quantity, $color, $item, $kittens) = @_;
...
}
Now imagine that those are four subroutines out of about 30. If the identically named variables are truly identical, but we start to get large parameter lists (this is frequent when we're passing parameters through a chain of functions and wind up with tramp data) then we have a "data clump". This clump can be grouped in one parameter object with a standard set of validations applied. This reduces the number of arguments to the various subroutines and makes it less likely that we'll forget to validate the variables.
In the above example, we may simply have one parameter object encapsulating the quantity, color, and item. We won't have thirty parameter objects. These objects are merely a refactoring tool to lower the amount of code duplication (see Martin Fowler's book on refactoring for more information on parameter objects).
Another benefit of parameter objects is that it might make it clear that another class is required. In the above example, it might be the case that quantity, color and item can be grouped into a "Product" class or something similar. However, when these parameters are constantly separated, such redesign of code may not be apparent.
Cheers,
Ovid
New address of my CGI Course.
Silence is Evil (feel free to copy and distribute widely - note copyright text) | [reply] [d/l] |