Garrett Goebel wrote:
> From: Branden [mailto:[EMAIL PROTECTED]]
> >
> > I was reading RFC 271 and thinking about this pre/post
> > handler thing. Why instead of having 2 subs, one for
> > pre and other for post condition, and having to deal
> > with things as strange as $_[-1], why don't we
> > have only one handler that calls the real sub?
> >
> > Instead of:
> >
> >  :  pre abc  {  do_pre_handler(@_);  }
> >  :  post abc {  do_post_handler($_[-1]); }
>
> Because the {...} following pre/post name... are the _real_ subs. There is
> no need for &do_pre_handler(@_) and &do_post_handler. On invokation of
&abc,
> both the anonymous subroutines for pre and post get passed the argument
list
> for the actual subroutine invokation, and $_[-1] which holds the eventual
> return value.
>
> I suppose the $_[-1] is there to avoid adding a new keyword like &value.
> This is all very similar to the Class::Contract module originally authored
> by Damian Conway... which actually does allow you to use &value within a
> post-condition instead of $_[-1].
>


I'm sorry, I didn't express myself well. When I wrote do_pre_handler and
do_post_handler I wanted to write a generic sub with statements, only didn't
have any statements in mind.

For example: suppose sub next_temp calculates a new temperature from an old
temperature

sub next_temp ($$) {
    my $this = shift;
    my $temp = shift;
    return $this->expression_with($temp);
}

Suppose it only handles Celsius, and we want to be able to handle Fahrenheit
and Kelvin also. We could do it by:


pre next_temp {
    my $temp = $_[1];
    if ($temp =~ /^(\d+)F$/) {         # Fahrenheit
        $temp = (($1 - 32) * 5) / 9;
    } elsif ($temp =~ /^(\d+)K$/) {    # Kelvin
        $temp = $1 - 273;
    } elsif ($temp !~ /^(\d+)C?$/) {   # Celsius
        die "Invalid temperature format: $temp";
    }
    $_[1] = $temp;
}


Well, that's OK, right? Now suppose you're inheriting it from a class that
has a pre-handler for next-temp that states:

pre next_temp {
    if ($_[1] < -5) {
        die "Too cold!!!";
    } elsif ($_[1] > 50) {
        die "Too hot!!!";
    }
}


Well, now analyze the inheritance behaviour. The converting pre-handler
would only be called if the temperature value were below -5 or above 50???
That's now DWIM for me!!!



Another one: suppose I want to return the value in the same format that it
was given to me. Damian told me that I could do it by:

pre next_temp {
    my $temp = $_[1];
    my $real_next_temp = (caller(0))[10];
    my $result;
    if ($temp =~ /^(\d+)F$/) {         # Fahrenheit
        $temp = (($1 - 32) * 5) / 9;
        $result = $real_next_temp->($temp);
        $_[-1] = ((9 * $result) / 5) + 32;
    } elsif ($temp =~ /^(\d+)K$/) {    # Kelvin
        $temp = $1 - 273;
        $result = $real_next_temp->($temp);
        $_[-1] = $result + 273;
    } elsif ($temp =~ /^(\d+)C?$/) {   # Celsius
        $_[-1] = $real_next_temp->($temp);
    }
    die "Invalid temperature format: $temp";
}



Well, that's OK, althought I think the conversion back would be a
post-handler thing. But that's a problem in the inheritance case, just as
before, as also if there are other pre-handlers that would be called after
this one, they wouldn't be called because this one would set $_[-1] and so
return this value instead of calling the real sub!!!

The $_[-1] thing is also a bad thing in that it hides the context the
function was called. What if the function was called in a list context?
$_[-1] would have an array reference? What if it had scalar context and
returned an array reference the same way???


What I propose is, instead of having pre- and post- handlers, we have pre-
and post- conditions that allow or don't allow a method to be called. That's
what's needed for DBC. The inheritance semantics would be preserved, and so
on, but allowing to change parameter list or even results is a dangerous
thing to me.

My proposal for these tasks (flock, temperature coversion) is to have a list
of subs attached to each real sub. When the real sub is called, Perl would
call each one of these subs, in order, passing them a pointer to the next
sub, that is the real sub for the last pre-sub.

For example:

sub next_temp ($$) {
    my $this = shift;
    my $temp = shift;
    return $this->expression_with($temp);
}


Now I'll attach the pre_post_handler:

sub next_temp_pre_handler {
    my ($this, $temp) = @_;
    my $next_sub_in_list = (caller(0))[10]; # or anything else...
    my $result;
    if ($temp =~ /^(\d+)F$/) {         # Fahrenheit
        $temp = (($1 - 32) * 5) / 9;
        $result = $next_sub_in_list->($temp);
        return ((9 * $result) / 5) + 32;
    } elsif ($temp =~ /^(\d+)K$/) {    # Kelvin
        $temp = $1 - 273;
        $result = $next_sub_in_list->($temp);
        return $result + 273;
    } elsif ($temp =~ /^(\d+)C?$/) {   # Celsius
        return $next_sub_in_list->($temp);
    }
    die "Invalid temperature format: $temp";
}
attach_pre_post_handler( \&next_temp,  \&next_temp_pre_handler  );


Now suppose I have attached another handler before this (and that means
called after this):

sub another_handler {
    $_[1]++;
    return (caller(0))[10]->(@_) - 10;
}
attach_pre_post_handler( \&next_temp,  \&another_handler  );


Well, this would be called ok, and would increment the temperature passed to
the method, and decrement its result in 10.

flock would work in the same way. We could even run the real sub inside an
eval, unlock the file and throw the exception if it exists later. I'm giving
an example of how to treat scalar/list-context also:

sub flock_handler {
    my ($this, $file, @params) = @_;
    my ($result, @result);
    flock $file, LOCK_EX;
    if (wantarray) {
        @result = eval { (caller(0))[10]->(@_); };
    } else {
        $result = eval { (caller(0))[10]->(@_); };
    }
    flock $file, LOCK_UN;
    die $@ if $@;
    return @result if wantarray;
    return $result;
}
attach_pre_post_handler( \&anithing_with_file,  \&flock_handler  );



This would be called in *any* circumstance, independent of inheritance or
other stuff.

What I mean is, DBC's pre- and post- conditions are VERY important, and
should be implemented, only shouldn't be implemented in a way that leads to
wrong stuff, like assuming that this same pre/post handler stuff could be
used to flock files or change parameter values, or any of such stuff. Its
inheritance behaviour completly makes it unstable.


Thanks again, Branden.

Reply via email to