in reply to Re^13: Assignable Subroutines
in thread Assignable Subroutines

So now we have a Perl that doesn't give you compile time checking and doesn't give you run time checking either!
That's why you have a test suite.
* Cumbersome - more code means more bugs
See above. Also, consider adding this attribute to many employees:

Cool, I can stop doing use strict now too because my test suite will catch my typos :-) I can buy the argument that weak typing and good testing can be better than strong typing (but only in those languages where strong typing is broken and forces you to do all manner of things to work around it or just switch it off - Java, C, C++) but that doesn't mean testing should replace all other error detection mechanisms.

The (il)logical conclusion of "testing is all you need" is that you should just write your tests and let your computer generate code at random until finally you get code that passes your tests.

OK, back to the examples. Firstly I'm not defending

$_->salary( $_->salary() * 1.02 );
as the ideal way of doing things. I'm one of the people in this thread arguing for Lvalue accessor methods, so the accessor examples should really be
$_->salary *= 1.02 for @employees;
and
for (@employees) { $_->salary *= 1.02; $_->frobnitz += 1.5; }
both of which are more readable and shorter than either of the other techniques.
Did I also mention that it's much easier for effects to dynamically change what operation they do? What if the user wants to add to a salary instead of applying a multiplier?
my $do_operation; # Defined elsewhere, possibly from user input my $by_ammount; # Also from user my @employees; # Also defined elsewhere my $op = $do_operation eq 'add' ? '+' : $do_operation eq 'mult' ? '*' : # And so on ; my $effect = Effect->new( [ salary => $by_ammount, $op ] ); $_->apply_effect( $effect ) for @employees;

What you're doing here is essentially creating a mini-language for operator application. Because Perl doesn't have a decent macro system (like lisp's) you then have to make people write code in an alreasy parsed form. There are several other ways to do this. Take a look at Tangram for a vary cool system that uses overloading and allows people to write their SQL clauses in Perl. For now, I'll provide two other ways to accomplish what you want, through functional programming or just using Perl itself

my $do_operation; # Defined elsewhere, possibly from user input my $by_ammount; # Also from user my @employees; # Also defined elsewhere my $op = $do_operation eq 'add' ? sub {$_[0] += $_[1]} : $do_operation eq 'mult' ? '*' : sub {$_[0] *= $_[1]} # And so on ; &$op($_->salary) for @employees;
my $do_operation; # Defined elsewhere, possibly from user input my $by_ammount; # Also from user my @employees; # Also defined elsewhere my $op = $do_operation eq 'add' ? '+' : $do_operation eq 'mult' ? '*' : # And so on ; eval "\$_->salary $op= \$by_amount" for @employees;
If Perl had proper macros (like lisp) then this would be much nicer.
* Slower

Then why are you using Perl objects at all? And your proposed solution is an AUTOLOAD everywhere, which is one of the last places checked when a method is called. That'll be even slower.

I was just pointing it out. The AUTOLOAD method is not slower by the way. It's much faster to find the AUTOLOAD method (which is then cached) and do 1 multiplication and 2 method calls than it is to jump into your effect object and do all the processing that is required to call the method, apply the relevant operation, and call the method again to store the result. That said, I'm not sure which would be faster between overridable lvalue accessors and effect objects.

Finally, I absolutely was not proposing empty AUTOLOAD methods as a solution to anything, I was just remarking that they can provide exactly the same problems as effect objects with a lot less hassle.

* Ignoring errors should not be the default

I don't consider it an error. Classes that don't have the given attribute ignore it. Those that do will use it. It's very polymorphic.

It's maximally polymorphic, any operation can be applied to any object whether it makes sense or not. Or maybe it's monomorpic because as far as callers can tell, there's only 1 type of object. For conistency one should also apply this principle to action methods too. So when I do

$reactor->insert_cooling_rods; $reactor->shut_down_liquid_cooling;
And if I was passed a reactor that doesn't have cooling rods then I should just continue on anyway. This does not seem very wise.
If you want it to be an error, there's nothing stopping you from defining an exception to be thrown when your object encounters an Effect attribute it doesn't use.

This is completely wrong. Now the object designer has to anticipate all of the infinte set of incorrect things that people might try to do with his object and explicitly make each one an error. Previously he only had to define the very small set of correct things people can do and anything outside that set was automatically and error.

Even worse, whether it's an error or not is now entirely in the hands of the called object when actually the caller is is the one that knows if it was OK to skip a certain field. The called object does not know (and shouldn't care) about the context and so is not in a position to be the final judge of what's an error.

It really seems to me that in your game, buildings should just ignore the speed message. But even if your RTS engine is a special case where all sorts of things should ignore all sorts of messages then maybe you're right to do what you're doing but that's still not an argument as to why software in general should do it too.

Replies are listed 'Best First'.
Re^15: Assignable Subroutines
by hardburn (Abbot) on Jan 27, 2005 at 19:55 UTC

    For now, I'll provide two other ways to accomplish what you want, through functional programming or just using Perl itself . . .

    Both examples require more functionality in the code higher up. It's not OO due to insufficient encapsulation.

    Again, if you don't want to use OO, that's fine, as there are plenty of places where OO is inappropriate. But if you are going to use it, do it right.

    This is completely wrong. Now the object designer has to anticipate all of the infinte set of incorrect things that people might try to do with his object and explicitly make each one an error.

    No, you don't:

    package Employee; . . . sub apply_effect { my ($self, $effects) = @_; # $effects->list returns a list of objects containing # the singular effects foreach my $effect ( $effects->list ) { my $attribute = $effect->attribute; throw_exception() unless exists $self->{$attribute}; # Rest of code } }

    Essentially a "deny by default" strategy. Now your test suite just needs to throw a few effects that are known to have attributes it's not going to use, check that exceptions were thrown, and you're set.

    It really seems to me that in your game, buildings should just ignore the speed message. But even if your RTS engine is a special case where all sorts of things should ignore all sorts of messages then maybe you're right to do what you're doing but that's still not an argument as to why software in general should do it too.

    The important point is that it's going to depend on the application. I was making a general framework, and in certain games it's going to make sense to ignore, while others might want to throw an error. It's none of my businesses to impose that on the game designer. Further, this strategy may be useful outside games, and I'd rather have a general framework that is easily modified rather than writing from scratch.

    I can see the argument for using (improved) lvalue subs for internal use (as dragonchild mentioned higher up the thread). But rarely/never outside the class.

    "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

      Both examples require more functionality in the code higher up. It's not OO due to insufficient encapsulation.
      I don't understand what you mean by "higher up". The code is almost identical to yours, just written directly in Perl, rather than some new and cumbersome operator application language.

      I also don't understand what you mean about "insufficient encapsulation". Why is that? If you mean that I shouldn't be calling $_->salary directly than you are arguing circularly as this whole discussion is about whether that's OK or not. If you don't mean that, please tell me what I should have encapsulated but didn't.

      As for the "deny by default" strategy, I get it free with Perl already, why do I need to write all that code to get it back again? Plus, allow by default is really easy to implement in "normal" Perl too so it's not like it was urgently in need of simplification. Also, it's a pain now to ignore some but not others and it's still under the control of the called object, not the caller. This may suit a framework (where things kind of become inside-out) but it's not the general case.

        I don't understand what you mean by "higher up".

        I mean the code to handle this is lifted outside the class and into the caller.

        The code is almost identical to yours, just written directly in Perl, rather than some new and cumbersome operator application language.

        I, for one, like the addition of small, domain specific languages. Your choice is to either learn an API or learn a (small) language. Choosing one or the other tends to force you twards different design decisions, but the cost of learning is not so different.

        I also don't understand what you mean about "insufficient encapsulation".

        Encapsulation is hiding data and behavior. Applying trvial mutators exposes data. It's therefore bad encapsulation. QED.

        But again, I think dragonchild's giveRaise() and givePromotion() methods are better than my own implementation.

        "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.