in reply to Re^10: Assignable Subroutines
in thread Assignable Subroutines
If you don't provide mutators, even for trivial fields, then you are just storing up pain for the future.
No, you fix your design so you don't need them in the first place.
In this case, I would make an "effect" object (I'm sure there is a Pattern for this). An effect stores values that can mutate other values when applied to another object.
my $employee = Employee->new( salary => 50_000 ); my $effect = Effect->new; $effect->set( salary => 1.02, '*' ); # A 2% raise $effect->set( frobnitz => 1.5, '+' ); # The frobnitz is up $employee->apply_effect( $effect );
The key here is you don't need to know for certain that a given field is implememented in Employee. The apply_effect method ignores any fields it doesn't recognize (like frobnitz). I'll give you that it is more lines of code than:
$employee->salary( $employee->salary * 1.02 );
But it is also a superior form of encapsulation.
"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.
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re^12: Assignable Subroutines
by fergal (Chaplain) on Jan 27, 2005 at 17:41 UTC | |
What does the following code do? All one can say it that it might raise the salary by 2% or it might do nothing at all, not even report an error. So now we have a Perl that doesn't give you compile time checking and doesn't give you run time checking either! The exact same effect can be achieved by having all your objects inherit from a base class with then go back to using Yes this would be an incredibly bad idea but it's exactly equivalent to using effect objects and it's a hell of a lot easier. There are some times when you want missing fields to be ignored but you should make that explicit in your code by doing something like
Problems with effect objects
The only real use I can think of for an effect object is to use it as a callback mechanism. Various people can add effects (or even remove or reorder effects) as the object is passed around, but I still don't see a reason to make it ignore errors by default. | [reply] [d/l] [select] |
by hardburn (Abbot) on Jan 27, 2005 at 18:15 UTC | |
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. See above. Also, consider adding this attribute to many employees:
Compared to:
Now we have to cram more into the for statement, while the Effect implementation lifted some of that complexity out. And we could even get rid of a line by having our Effect constructor take what we were passing to the set() method:
Expanding this further, what about setting multiple attributes?
Compared to:
Now the line-count complexity isn't as clear. 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?
Other than getting what operation we want to perform (which we have to do in any case), this code is barely more complex than the first case. Key note: OOP is not about decreasing complexity. It's about keeping it manageable.
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 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. 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. I'm working on a framework for building games in Perl that uses just such a design. The framework provides a domain-specific language that is used to build a series of Perl objects (so you can use this framework, at least at a basic level, without any Perl knowledge). Effects can be created in this language just like any other item in your game. In an RTS game, buildings could be considered units that don't move. You can shoot them, repair them, and add upgrades using whatever methods the game designer provides, just like any other unit in the game. In our RTS example, a building wouldn't have a "speed" attribute like a normal unit (or it would have a speed of 0). You could also have a weapon that makes a unit travel at 50% of normal speed. Fire such a weapon on a building would, at the code level, apply the effect of the weapon to the building. The building would then simply ignore the effect. The player is free to keep on shooting if they want. Or the interface designer could make it difficult (need to use the force-fire feature or some such) to shoot such a weapon at a building, but that's a matter for the interface, not for the level I'm currently working at. "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. | [reply] [d/l] [select] |
by fergal (Chaplain) on Jan 27, 2005 at 19:28 UTC | |
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 bugsSee 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 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 and 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? 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
If Perl had proper macros (like lisp) then this would be much nicer. * 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 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 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. | [reply] [d/l] [select] |
by hardburn (Abbot) on Jan 27, 2005 at 19:55 UTC | |
by fergal (Chaplain) on Jan 27, 2005 at 21:10 UTC | |
| |
|
Re^12: Assignable Subroutines
by fergal (Chaplain) on Jan 27, 2005 at 17:44 UTC | |
While I'm at it, a couple of questions. Where is this "no mutators" idea coming from? What exactly is the damage that results from having mutators? I've read the Java world article and all it's saying is don't create public mutuators for fields that are for internal use only. Blindingly obvious advice but I can understand why it may have been needed. I imagine the sequence of events in Javaland to be That last step would probably be performed by newer programmers, copying without understanding, seeing that public fields are not being used at all and that private + accessor seems to be "correct". | [reply] |
by dragonchild (Archbishop) on Jan 27, 2005 at 18:20 UTC | |
No, not because you shoved values into the object's head. Because you had to know what values to shove into its head into order to make sure the series of statements you ran did what you wanted it to do. There is always a better way to write an object than to use mutators. Except, it takes more time in the design phase and Lord&Lady know that no-one ever has time to design. Being right, does not endow the right to be rude; politeness costs nothing. | [reply] |
by fergal (Chaplain) on Jan 27, 2005 at 19:40 UTC | |
or
The whole problem here comes originally from people exposing highly interrelated fields as independently manipulable things when they shouldn't and in that case mutators are wrong. The whole idea is to be able to have invariants that are always true about an object and giving access to individual fields can sometimes allow things outside the object to leave the object in a state where the invariant is no longer true. I totally appreciate that. But sometimes objects have fields that are independent of all other fields and can be twiddled by anyone without breaking an invariant. There are only 2 choices for implementing these - direct field access or mutator and in most languages direct access is bad because it cannot be overridden in the future so you use a mutator. | [reply] [d/l] [select] |
by dragonchild (Archbishop) on Jan 27, 2005 at 19:58 UTC | |
by fergal (Chaplain) on Jan 27, 2005 at 20:53 UTC | |
by hardburn (Abbot) on Jan 27, 2005 at 18:22 UTC | |
The "no trivial mutators" idea comes from the fact that if you wanted a struct, then you should have used a struct and left the OO baggage behind. Java gets stuck on the fact that everything has to be an object, which means that when a struct really is the best way to do something (as it is in many simple programs), you still have to define it in terms of an object. That's an inheirent difficulty in single-paradigm languages. We don't have that problem in Perl, but it's still good advice that if you're going to trouble yourself with OO, then you should really do OO. I'm not going to demand that you apply OO everywhere, only that if you use it, you use it correctly. Bad OO is often worse than not using it in the first place. "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. | [reply] |
by fergal (Chaplain) on Jan 27, 2005 at 20:42 UTC | |
| [reply] |
by hardburn (Abbot) on Jan 27, 2005 at 20:56 UTC | |
by fergal (Chaplain) on Jan 27, 2005 at 21:17 UTC | |
| |