Steven D'Aprano <[EMAIL PROTECTED]> writes: > On Sat, 10 Dec 2005 13:33:25 -0500, Mike Meyer wrote: >> Steven D'Aprano <[EMAIL PROTECTED]> writes: >>>> In particular, >>>> you can get most of your meaningless methods out of a properly >>>> designed Coordinate API. For example, add/sub_x/y_ord can all be >>>> handled with move(delta_x = 0, delta_y = 0). >>> >>> Here is my example again: >>> >>> [quote] >>> Then, somewhere in my application, I need twice the >>> value of the y ordinate. I would simply say: >>> >>> value = 2*pt.y >>> [end quote] >>> >>> I didn't say I wanted a coordinate pair where the y ordinate was double >>> that of the original coordinate pair. I wanted twice the y ordinate, which >>> is a single real number, not a coordinate pair. >> >> Here you're not manipulating the attribute to change the class - >> you're just using the value of the attribute. That's what they're >> there for. > > [bites tongue to avoid saying a rude word] > > That's what I've been saying all along! > > But according to the "Law" of Demeter, if you take it seriously, I > mustn't/shouldn't do that, because I'm assuming pt.y will always have a > __mul__ method, which is "too much coupling". My Coordinate class > must/should create wrapper functions like this:
I think you've misunderstood the LoD. In particular, 2 * pt.y doesn't necessarily involve violating the LOD, if it's (2).__add__(pt.y). If it's pt.y.__add__(2), then it would. But more on that later. >>> Do you lie awake at nights worrying that in Python 2.6 sys.stdout will >>> be renamed to sys.standard_output, and that it will no longer have a >>> write() method? According to the "law" of Demeter, you should, and the >>> writers of the sys module should have abstracted the fact that stdout >>> is a file away by providing a sys.write_to_stdout() function. That is >>> precisely the sort of behaviour which I maintain is unnecessary. >> >> And that's not the kind of behavior I'm talking about here, nor is it >> the kind of behavior that the LoD is designed to help you with (those >> are two different things). > > How are they different? Because one is a class and the other is a module? > That's a meaningless distinction: you are still coupled to a particular > behaviour of something two levels away. If the so-called Law of Demeter > makes sense for classes, it makes sense for modules too. And here's where I get to avoid saying a rude word. I'm not going to chase down my original quote, but it was something along the lines of "You shouldn't reach through multiple levels of attribute to change things like that, it's generally considered a bad design". You asked why, and I responded by pointing to the LoD, because it covers that, and the underlying reasons are mostly right. I was being lazy, and took an easy out - and got punished for it by winding up in the position of defending the LoD. My problem with the original code wasn't that it violated the LoD; it was that it was reaching into the implementation in the process, and manipulating attributes to do things that a well-designed API would do via methods of the object. The LoD forces you to uncouple your code from your clients, and provide interfaces for manipulating your object other than by mucking around with your attribute. I consider this a good thing. However, it also prevents perfectly reasonable behavior, and there we part company. And of course, it doesn't ensure good design. As you demonstrated, you can translate the API "manipulate my guts by manipulating my attributes" into an LoD compliant API by creating a collection meaningless methods. If the API design was bad to begin with, changing the syntax doesn't make it good. What's a bad idea hefre is exposing parts of your implementation to clients so they can control your state. Whether you do that with a slew of methods for mangling the implementation, or just grab the attribute and use it is immaterial. The LoD tries to deal with this by outlawing such manipulation. People respond by mechanically translating the design into a form that follows the law. Mechanically translating a bad design into compliance with a design law doesn't make it a good design. Instead of using a vague, simple example with a design we don't agree on, let's try taking a solid example that we both (I hope) agree is good, and changing it to violate encapsulation. Start with dict. Dict has an argumentless method, which means we could easily express it as an attribute: keys. I'm going to treat it as an attribute for this discussion, because it's really immaterial to the point (and would work in some languages), but not to people's perceptions. Given that, what should mydict.keys.append('foobar') do? Given the current implementation, it appends 'foobar' to a list that started life as a list of the keys of mydict. It doesn't do anything to mydict; in particular, the next time you reference mydict.keys, you won't get back the list. This is a good design. If mydict.keys.append('foobar') were the same as "mydict['foobar'] = None", that would be a bad design. Now suppose you want the keys in sorted order? That's a common enough thing to want. The obvious way to get it is to get the list of keys and to sort them. The LoD isn't clear on that (or maybe I failed to read it properly), as you're allowed to call methods on objects that you created. Did you create the list of keys? Did mydict? Which is allowed? I dunno. On the other hand, I don't have a problem with it. The keys feature gives you a picture of part of the dictionary. What you do with the picture after you get it is up to you - it isn't going to change mydict. Once you've got the list, it's no longer part of mydict, so invoking methods on it don't violate encapsulation, so there's no problem with it. Back to your question about sys.stdout. I said the LoD says it's ok because I think a module is a collection, meaning sys.stdout is an element of a collection, and it's ok to call methods on them. Others may disagree about modules being collections. I say the call is ok because sys.stdout is a value from sys, and manipulating it doesn't change the internal state of the module sys. I think I've explained the difference between what I'm saying and the what LoD says. I think there's a relationship between the two; I'm just not sure what it is. > [snip] > >> Again, this is *your* API, not mine. You're forcing an ugly, obvious API >> instead of assuming the designer has some smidgen of ability. > But isn't that the whole question? Should programmers follow slavishly the > so-called Law of Demeter to the extremes it implies, even at the cost of > writing ugly, unnecessary, silly code, or should they treat it as a > guideline, to be obeyed or not as appropriate? I believe the correct answer is "practicality beats purity". On the other hand, I'll continue to argue that following the LoD - or at least parts of it - only leads to ugly, unnecessary, silly code if your design was bad in the first place. Not following the LoD doesn't make the design good - it just means you write a lot less code in creating your bad design. > Doesn't Python encourage the LoD to be treated as a guideline, by allowing > class designers to use public attributes instead of forcing them to write > tons of boilerplate code like some other languages? Python encourages damn near everything to be treated as a guideline. It's one of the things I like about the language - if I need a hack *now* that fixes a problem, I don' have to fight the language, I can just do it. I argue with people who try and create classes that break that because they think "it enforces good style". The thing about the tons of boilerplate code is that it's enforcing an arbitrary rule in the name of enforcing "good design". But it doesn't make the design good. In particular, if letting someone write "obj.foo.mutate(value)" to manipulate obj is bad design, then making them write "obj.mutate_foo(value)" doesn't mean the design is good. >> I've >> already pointed out one trivial way to deal with this, and there are >> others. > Mike, the only "trivial way to deal with this" that you have pointed out > was this: > "For example, add/sub_x/y_ord can all be handled with move(delta_x = 0, > delta_y = 0)." > That's a wonderful answer *for the wrong question*. I thought I explained > that already. If you did, I must have missed it. But maybe I've been answering the wrong question all along. <mike -- Mike Meyer <[EMAIL PROTECTED]> http://www.mired.org/home/mwm/ Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information. -- http://mail.python.org/mailman/listinfo/python-list