Another newbie question

A

Alex Martelli

Mike Meyer said:
It's a trivial example. Incremental extra work is pretty much
guaranteed to be trivial as well.

You appear not to see that this triviality generalizes. Given any set
of related attributes that among them determine non-redundantly and
uniquely the value of an instance (mathematically equivalent to forming
a primary key in a normal-form relational table), if it's at all
interesting to let those attributes be manipulated for a mutable
instance, if must be at least as important to offer an alternative ctor
or factory to create the instance from those attributes (and that
applies at least as strongly to the case of immutable instances).

Given that you have such a factory, *whatever its internal complexity*,
the supplementary amount of work to produce a setter for any subset of
the given attributes, whatever the internal representation of state used
for the instance, is bounded and indeed trivial:
a. create a new instance by calling the factory with the values of the
attributes being those of the existing instance (for attributes which
are not being changed by the current method) and the new value being set
(for attributes which are being set by the current method);
b. copy the internal state (whatever its representation may be) from the
new instance to the existing one (for many cases of classes with mutable
instances, you will already have a 'copyFrom' method doing this, anyway,
because it's useful in so many other usage contexts).

That's it -- you're done. No *DIFFICULTY* -- indeed, a situation close
enough to boilerplate that if I found myself writing a framework with
multiple such classes I'd seriously consider refactoring it upwards into
a custom metaclass or the like, just because I dislike boilerplate as a
matter of principle.

The claim is that there exists cases where that's true. This cases
demonstrates the existence of such cases. That the sample is trivial
means the difficulty is trivial, so yeah, it's a miserable poster
child. But it's a perfectly adequate existence proof.

You appear to be laboring under the serious misapprehension that you
have demonstrate any DIFFICULTY whatsoever in writing mutators
(specifically attribute-setters). Let me break the bad news to you as
diplomatically as I can: you have not. All that your cherished example
demonstrates is: if you're going to write a method, that method will
need a body of at least one or two statements - in this case, I've shown
(both in the single concrete example, and in the generalized code) that
IF a set of attributes is interesting enough to warrant building a new
instance based on them (if it is totally uninteresting instead, then
imagining that you have to allow such attributes to be MUTATED on an
existing instance, while forbidding them to be ORIGINALLY SET to create
a new instance, borders on the delusional -- what cases would make the
former but not the latter an important functionality?!), THEN
implementing mutators (setters for those attributes) is trivially EASY
(the converse of DIFFICULT) -- the couple of statements in the attribute
setters' bodies are so trivial that they're obviously correct, assuming
just correctness of the factory and the state-copying methods.


Alex
 
S

Steven D'Aprano

On Sat, 10 Dec 2005 22:56:12 -0500, Mike Meyer wrote:

[snip]
The claim is that there exists cases where that's true. This cases
demonstrates the existence of such cases. That the sample is trivial
means the difficulty is trivial, so yeah, it's a miserable poster
child. But it's a perfectly adequate existence proof.

Huh?

As I see it:

Claim: doing X makes Y hard.
Here is an example of doing X where Y is easy.
Therefore that example proves that doing X makes Y hard.

Perhaps I've missed some subtle meaning of the terms "demonstrates" and
"existence proof".
 
S

Steven D'Aprano

I find this a strange interpretation.

sys is a module, not an instance. Sure you can use the same notation
and there are similarities but I think the differences are more
important here.

The fact that sys is a module and not a class is a red herring. If the
"Law" of Demeter makes sense for classes, it makes just as much sense for
modules as well -- it is about reducing coupling between pieces of code,
not something specific to classes.

The point of the "Law" of Demeter is to protect against changes in objects
more than one step away from the caller. You have some code that wants to
write to stdout, which you get from the sys module -- that puts sys one
step away, so you are allowed to rely on the published interface to sys,
but not anything further away than that: according to the so-called "law",
you shouldn't/mustn't rely on things more than one step away from the
calling code.

One dot good, two dots bad.

Assuming that stdout will always have a write() method is "bad" because it
couples your code to a particular implementation of stdout: it assumes
that it will always be a file-like object with a write method. What if the
maintainer of sys decides to change it?

Arguing that "this will never happen, it would break too much code" is
*not* good enough, not for the Law of Demeter zealots -- they will argue
that the only acceptable way to code is to create an interface to the
stdout object one level away from the calling code. Instead of calling
sys.stdout.write() (unsafe, what if the stdout object changes?) you must
use something like sys.write_to_stdout() (only one level away).

The fact that people can and do break the "Law" of Demeter all the time,
with no harmful effects, shows that it is no law at all. It is a
*guideline*, and as a guideline I've heard worse ideas than "keep your
options open". That's what it really boils down to: if you specify an
interface of helper functions, you can change your implementation, at the
expense of doing a lot extra work now just in case you will need it later.
But that's not a virtue in and of itself -- it is only good if you
actually intend to change your implementation, or at least think you might
want to some day, and then only if the work needed to write your
boilerplate is less than the work needed to adapt to the changed
implementation.

[snip]
Yikes. I would never do that. Doing so would tie my code unnecesary
close to yours and would make it too difficult to change to an other
class with a different implementation like one using tuples or lists
instead of a seperate x and y instances.

Do you really think that my class and some other class written by
another person will have the same API? If you change from my class to
another class, the chances are that the interfaces will be different
unless the second class writer deliberately emulated my class interface.

To class users, there is *no* difference in consequences between me
changing my published API by removing named attributes x and y from my
class, and me changing my published API by removing or changing methods.

Who in heavens name would need those? Maybe there is no x or y because
the implementation uses a list or a tuple, maybe the implementation uses
polar coordinates because that is more usefull for the application it
was planned for.

And maybe it isn't a Coordinate class at all, hmmm?

An ordinary, Cartesian, real-valued Coordinate is a pair of ordinates, an
X and Y ordinates. That's what it *is* -- a coordinate class without X and
Y ordinates isn't a coordinate class, regardless of how they are
implemented (via properties, named attributes, or a whole bucketful of
helper functions).

I'm not interested in polar coordinates, lists, dicts, red-black trees,
complex-valued infinite dimensional vectors, byte streams or any other
class. If I wanted one of those, I'd write *that* class and I wouldn't
need to access the X and Y ordinates. But since I want a two dimensional
Cartesian coordinate class, I must have *some* way of accessing the X and
Y ordinates, otherwise it isn't a two dimensional Cartesian coordinate
class.

The question is, MUST I write a whole pile of boilerplate functions?
According to the Law of Demeter, I must, just in case somebody changes the
definition of float and suddenly code like value = 2*pt.y stops working.
In my opinion, that's taking abstraction to ridiculous extremes.

I'm not saying that there is never any reason to write getters and
setters or similar boilerplate. If I suspect (or fear) that the
implementation is going to change after my API is nailed down, then it is
a good idea to write an intermediate public level so I can change the
internal implementation at a later date. That good practice. Bad practice
is to pretend that the boilerplate code making that intermediate level is
cost-free, and that therefore one must always use it.

[snip]
No he would have none.

Do you really mean to tell me that the class writer can change their
public interface without breaking code?
 
P

Paul Rubin

Steven D'Aprano said:
The fact that sys is a module and not a class is a red herring. If the
"Law" of Demeter makes sense for classes, it makes just as much sense for
modules as well -- it is about reducing coupling between pieces of code,
not something specific to classes.

I don't see that. If a source line refers to some module you can get
instantly to the module's code. But you can't tell where any given
class instance comes from. That's one of the usual criticisms of OOP,
that the flow of control is obscured compared with pure procedural
programming.
One dot good, two dots bad.

Easy to fix. Instead of sys.stdout.write(...) say

from sys import stdout

from then on you can use stdout.write(...) instead of sys.stdout.write.
 
M

Mike Meyer

Steven D'Aprano said:
Steven D'Aprano said:
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:

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.
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.
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
 
M

Mike Meyer

You appear to be laboring under the serious misapprehension that you
have demonstrate any DIFFICULTY whatsoever in writing mutators
(specifically attribute-setters). Let me break the bad news to you as
diplomatically as I can: you have not. All that your cherished example
demonstrates is: if you're going to write a method, that method will
need a body of at least one or two statements - in this case, I've shown
(both in the single concrete example, and in the generalized code) that
IF a set of attributes is interesting enough to warrant building a new
instance based on them (if it is totally uninteresting instead, then
imagining that you have to allow such attributes to be MUTATED on an
existing instance, while forbidding them to be ORIGINALLY SET to create
a new instance, borders on the delusional -- what cases would make the
former but not the latter an important functionality?!), THEN
implementing mutators (setters for those attributes) is trivially EASY
(the converse of DIFFICULT) -- the couple of statements in the attribute
setters' bodies are so trivial that they're obviously correct, assuming
just correctness of the factory and the state-copying methods.

It's not my cherished example - it actually came from someone
else. That you can change the requirements so that there is no extra
work is immaterial - all you've done is shown that there are examples
where that don't require extra work. I never said that such examples
didn't exist. All you've shown - in both the single concrete example
and in a generalized case - is that any requirement can be changed so
that it doesn't require any extra work. This doesn't change the fact
that such cases exist, which is all that I claimed was the case.

<mike
 
M

Mike Meyer

Steven D'Aprano said:
Huh?
As I see it:
Claim: doing X makes Y hard.

Harder, not hard.
Here is an example of doing X where Y is easy.

Y is very easy in any case. Making it incrementally harder doesn't
make it hard - it's still very easy.
Perhaps I've missed some subtle meaning of the terms "demonstrates" and
"existence proof".

I think you missed the original claim.

<mike
 
S

Steve Holden

Paul said:
I don't see that. If a source line refers to some module you can get
instantly to the module's code. But you can't tell where any given
class instance comes from. That's one of the usual criticisms of OOP,
that the flow of control is obscured compared with pure procedural
programming.




Easy to fix. Instead of sys.stdout.write(...) say

from sys import stdout

from then on you can use stdout.write(...) instead of sys.stdout.write.

The fact that you are prepared to argue for one of two mechanisms rather
than the other based simply on the syntax of two semantically equivalent
references is unworthy of someone who knows as much about programming as
you appear to do.

The "Law" of Demeter isn't about *how* you access objects, it's about
what interfaces to objects you can "legally" manipulate without undue
instability across refactoring. In other words, it's about semantics,
not syntax. And it's led a lot of Java programmers down a path that
makes their programs less, not more, readable.

Python's ability to let the programmer decide how much encapsulation is
worthwhile is one of its beauties, not a wart.

regards
Steve
 
A

Alex Martelli

Mike Meyer said:
It's not my cherished example - it actually came from someone

You picked it to (try and fail to) show that there is DIFFICULTY, which
I showed there isn't.
else. That you can change the requirements so that there is no extra
work is immaterial - all you've done is shown that there are examples
where that don't require extra work. I never said that such examples
didn't exist. All you've shown - in both the single concrete example
and in a generalized case - is that any requirement can be changed so
that it doesn't require any extra work. This doesn't change the fact
that such cases exist, which is all that I claimed was the case.

Untrue: you claimed that the specific API (allowing attribute-setting)
"makes changing the object more difficult", not the obvious fact that
"there exist APIs so badly designed that they make changing more
difficult". And I showed that, in the GENERAL case, since attributes
worth being made assignable are obviously also worth being made settable
in a constructor of some kind, having settable attributes doesn't and
cannot introduce any DIFFICULTY -- the API with settable attributes only
requires trivial methods, ones presenting no difficulty whatsoever,
which delegate all the real work to methods you'd need anyway
(particularly the obviously-needed constructor or factory).

So, I claim I have totally disproven your claims about difficulty
("extra work", as you're trying to weaselword your way out, might be
writing one or two trivial lines of code, but that's not DIFFICULT, and
the claim you originally made was about DIFFICULTY, not tiny amounts of
trivially easy "extra work" -- as I already mentioned, obviously ANY
method you add is "extra work" for you compared to not adding it, but
the interesting question is whether that entails any DIFFICULTY).

My claim hinges on the fact that constructors are important -- more so,
of course, for immutable instances, but even in the mutable case it's
pretty bad design if the ONLY way to have an instance in the state you
know is right is to make it in a state that's wrong and then call
mutators on it until its state is finally right... obviously it's
important to avoid imposing this busywork on all users of the class. If
you further weaken your claim to "it's possible to design so badly that
everybody involved faces more work and difficulties", I'll most
obviously agree -- but such bad designs need not involve any
attribute-setters, nor does including attribute-setters imply or even
suggest that a design is bad in this way!


Alex
 
A

Alex Martelli

Mike Meyer said:
Harder, not hard.

The specific wording you used was "MORE DIFFICULT".

Y is very easy in any case. Making it incrementally harder doesn't
make it hard - it's still very easy.

If it's very easy, then going out of your way, as you did, to claim it's
"MORE DIFFICULT" (you did not use the words "incrementally harder") is
rather weird. There's no DIFFICULTY -- sure, if you have ANY one extra
trivial method there IS ``extra work'' (a few seconds to write the
trivial method and its unittest), but no extra DIFFICULTY.

Moreover, I believe I vindicated attribute-setters (and their lack of
difficulty) specifically by pointing out the trivial pattern which lets
you make them easily, by using a constructor that you must have anyway
in a good design (if attributes are worth setting on the fly, they're
worth setting at the birth of an instance) and a state-copying method
(which is always a good idea for mutable-instance classes). Assuming
this wasn't obvious at the start to all readers, I may thus have hoped
to have taught something to some reader -- an unusual and pleasant
fallout from a mostly "polemical" thread, since often such threads are
not very instructive.

On that vein, I'll continue by pointing out that there may well be
opportunities for optimization -- constructing a new instance is easy,
but in some cases, depending on the implementation details, there may be
faster approaches. That's most of the case for our using languages with
modifiable data rather than pure functional ones, after all...: that
changing data (rather than always having to make new objects) sometimes
affords better performance. Still, let's not optimize *prematurely*!-)


Alex
 
M

Mike Meyer

You picked it to (try and fail to) show that there is DIFFICULTY, which
I showed there isn't.

No, you showed you could change the example so there is no extra
difficulty.
Untrue: you claimed that the specific API (allowing attribute-setting)
"makes changing the object more difficult", not the obvious fact that
"there exist APIs so badly designed that they make changing more
difficult".

Except you haven't shown that the API was badly designed. You can't
show that it's badly designed, because you don't know the requirements
that the API is meeting.
And I showed that, in the GENERAL case, since attributes
worth being made assignable are obviously also worth being made settable
in a constructor of some kind,

But we're not dealing with a general case, we're dealing with a
specific case. Just because you can't think of cases where an
attribute being settable doesn't mean it needs to be settable in a
constructor doesn't mean they don't exist.
So, I claim I have totally disproven your claims about difficulty
("extra work", as you're trying to weaselword your way out, might be
writing one or two trivial lines of code, but that's not DIFFICULT, and
the claim you originally made was about DIFFICULTY, not tiny amounts of
trivially easy "extra work" -- as I already mentioned, obviously ANY
method you add is "extra work" for you compared to not adding it, but
the interesting question is whether that entails any DIFFICULTY).

Actually, the original claim was "more difficult". You've done your
usual trick of reaching an invalid conclusion from what someone said,
then acting as if that's what they said. Congratulations, you've
successfully beaten up the straw man you created.

<mike
 
A

Alex Martelli

Mike Meyer said:
Except you haven't shown that the API was badly designed. You can't
show that it's badly designed, because you don't know the requirements
that the API is meeting.

I can show that an API is badly designed *whatever requirements it might
be intended for* if it's self-contradictory: containing a way to CHANGE
an attribute to some different value, but not containing any way to SET
THAT ATTRIBUTE TO THE RIGHT VALUE from the beginning, is inherently an
indicator of bad design, because it needlessly imposes more work on the
API's user and forces objects to pass through a transient state in which
their attributes are WRONG, or MEANINGLESS.

But we're not dealing with a general case, we're dealing with a
specific case. Just because you can't think of cases where an
attribute being settable doesn't mean it needs to be settable in a
constructor doesn't mean they don't exist.

The burden of the proof is on you, of course: show a design situation
where it's RIGHT to force API users to do extra work and lead objects
through states they're *NOT* meant to be in, because there is no way to
build the object correctly from the start, but rather the object must be
built in a wrong state and then later coerce it to the state you knew
was right since the beginning.

There may be languages which are so feeble as to force such behavior
(e.g., languages where every new instance has every attribute forced to
null even where it makes no sense for a certain attribute to ever be
null) but that applies to neither Eiffel nor Python, and all it shows is
that some languages are seriously lacking in the tools to allow proper
designs to be implemented, not that "all objects must always be
generated in the WRONG state" can ever be the RIGHT design.

Actually, the original claim was "more difficult". You've done your
usual trick of reaching an invalid conclusion from what someone said,
then acting as if that's what they said. Congratulations, you've
successfully beaten up the straw man you created.

Right: I claim, on the other hand, that YOU are weaselwording, by trying
to claim that any class with one extra method is thereby "MORE
DIFFICULT" to write -- equating having to write one or two lines of
trivial code with "MORE DIFFICULT" would make the "more difficult"
totally bereft of any useful meaning in whatever context.

I'm currently in an interesting job role, known as "uber technical
lead", which is meant to be a sort of a cross between technical manager
and ubergeek-guru. Fortunately, my reports are all people of technical
excellence as well as personal integrity, so, should I ever ask one of
them to explain why he or she did X and not Y, I fully trust they won't
try to explain that "doing Y would have been more difficult" when the
reality is that it would have involved a line of two of trivial code...
if they did, I can assure you that the consequences might be
interesting. (Good think I can and do trust them to say, should such a
situation ever arise, "DUH! -- I just didn't think of it!", and go fix
their code forthwith... just as they've often heard ME say,
apologetically, in the much more frequent situations where my objections
to some design were misconceived... so, my modest management abilities
will not be put to such a difficult test in the foreseeable future;-).


Alex
 
M

Mike Meyer

I can show that an API is badly designed *whatever requirements it might
be intended for* if it's self-contradictory: containing a way to CHANGE
an attribute to some different value, but not containing any way to SET
THAT ATTRIBUTE TO THE RIGHT VALUE from the beginning, is inherently an
indicator of bad design, because it needlessly imposes more work on the
API's user and forces objects to pass through a transient state in which
their attributes are WRONG, or MEANINGLESS.

Nope. If the requirements are that all objects start in the same
meaningful state, then you simply create them in that state. There's
no need to provide a facility to to set the initial state, and they
never go through a meaningless state either.
The burden of the proof is on you, of course: show a design situation
where it's RIGHT to force API users to do extra work and lead objects
through states they're *NOT* meant to be in, because there is no way to
build the object correctly from the start, but rather the object must be
built in a wrong state and then later coerce it to the state you knew
was right since the beginning.

You're doing it again - I never claimed that there was any such API
requirement. You've reached this conclusion on your own, by adding
requirements to the design that I never discussed. If you want to
someone to proof this straw man, you'll have to do it yourself.
Right: I claim, on the other hand, that YOU are weaselwording, by trying
to claim that any class with one extra method is thereby "MORE
DIFFICULT" to write -- equating having to write one or two lines of
trivial code with "MORE DIFFICULT" would make the "more difficult"
totally bereft of any useful meaning in whatever context.

As I already explained, the entire change was trivial, so any extra
work is of course trivial. This extra work is exactly what I meant
when I said "more difficult". You want to play semantic games, and
argue that one trivial change can't be "more difficult" than another,
feel free to do so. But do realize that you're disproving your
strawmen, not my statement.

<mike
 
A

Antoon Pardon

Op 2005-12-11 said:
Do you really think that my class and some other class written by
another person will have the same API?

If both writers try to implement the same kind of object I would
think the API would be very similar yes.
If you change from my class to
another class, the chances are that the interfaces will be different
unless the second class writer deliberately emulated my class interface.

So, lets say I have one class where you can do P1 + P2 and an other
class where you have to do P1.move(P2). If it is basically the
same kind of class but with a different API I just write a wrapper
and I am done unless of course I messed with the internals and
the internals in the second class are vastly different.
To class users, there is *no* difference in consequences between me
changing my published API by removing named attributes x and y from my
class, and me changing my published API by removing or changing methods.

Yes there is. Methods are just names, if you just have different names
for the same functionality all you need is write a wrapper to translate
one name into an other.

If you no longer have an x and y attribute but a 2 element tuple,
then things aren't that easy to repair.
And maybe it isn't a Coordinate class at all, hmmm?

Indeed it isn't. It is usually a Point class.
An ordinary, Cartesian, real-valued Coordinate is a pair of ordinates, an
X and Y ordinates. That's what it *is* -- a coordinate class without X and
Y ordinates isn't a coordinate class, regardless of how they are
implemented (via properties, named attributes, or a whole bucketful of
helper functions).

That is why a coordinate class is a bad idea. It mentions an
implementation in what should be an abstract idea like a 2D point.

In that case if you find out that you are manipulating your objects
in ways, for which polar coordinates are better, you can transparantly
change the implementation.
 
S

Steven D'Aprano

Indeed it isn't. It is usually a Point class.


That is why a coordinate class is a bad idea. It mentions an
implementation in what should be an abstract idea like a 2D point.

In that case if you find out that you are manipulating your objects
in ways, for which polar coordinates are better, you can transparantly
change the implementation.

That's a great idea Antoon, but you don't go far enough. Why limit
yourself to something as concrete as a pair of floats? What we actually
need is an even more abstract class, one which can hold an arbitrary
number of ordinates, not just two. And why limit ourselves to floats? What
if the user decides that he wants to specify ordinates as written English
numbers like the Morse Code for "thirty-seven point three four", coded in
base64?

For that matter, now that we have an arbitrary number of ordinates, why
limit yourself to list implementation? Perhaps a better implementation is
a tree structure, or an orchard, or some sort of mapping? Or some hybrid
of all three.

And the methods, well, the methods. It is so limiting to be forced into
one specific API, with names like instance.move(), rotate(), reflect() and
so forth. What if I should change my mind, and decide what I really need
is a message-passing model instead? We better write some more code
isolating the methods from the user, making the class even more abstract
again, just in case we should ever choose to change those methods'
interface.

Heaven forbid that we should actually decide on a model for our class,
ever. Sure, we'll end up having to implement a Turing-complete programming
language as our class, but I think we'll all agree that that cost is a
small price to pay for something which is sufficiently abstract.
 
A

Antoon Pardon

Op 2005-12-12 said:
That's a great idea Antoon, but you don't go far enough. Why limit
yourself to something as concrete as a pair of floats? What we actually
need is an even more abstract class, one which can hold an arbitrary
number of ordinates, not just two.

In point of fact, the class I have can do just that.
And why limit ourselves to floats? What
if the user decides that he wants to specify ordinates as written English
numbers like the Morse Code for "thirty-seven point three four", coded in
base64?

How the user specifies his values and how they are internally stored
are two entirely different issues. The fact that the use specifies
his numbers in Morse Code or written out in words doesn't imply
they have to be stored in that form. Just as the user supplying his
points with x,y coordinates doesn't imply the implementation has
to work with carthesion coordinates.
For that matter, now that we have an arbitrary number of ordinates, why
limit yourself to list implementation? Perhaps a better implementation is
a tree structure, or an orchard, or some sort of mapping? Or some hybrid
of all three.

Indeed different kind of applications work better with different
kind of implementations. That is the whole point, use the same
API for the same functionality even if the implementation is
different, so I can solve the same kind of problem with the same
code, independant on whether I have 2D point 3D points or maybe
sparse 10 000 000D points.
And the methods, well, the methods. It is so limiting to be forced into
one specific API, with names like instance.move(), rotate(), reflect() and
so forth. What if I should change my mind, and decide what I really need
is a message-passing model instead? We better write some more code
isolating the methods from the user, making the class even more abstract
again, just in case we should ever choose to change those methods'
interface.

Heaven forbid that we should actually decide on a model for our class,
ever.

There is a difference between deciding on a model and exposing your
model. If you are working with certain kinds of objects, the solution
should more or less be independant of the model chosen to implement
the object. If you need to expose the model in order to solve particular
problems with your objects, I would think you either have chosen the
wrong kind of objects or a bad implementation of them to solve your
problem.
 
J

james.moughan

Mike said:
Exposing them doesn't make making changes more difficult. Allowing
them to be used to manipulate the object makes some changes more
difficult. Properties makes the set of such changes smaller, but it
doesn't make them vanish.

Take our much-abused coordinate example, and assume you've exposed the
x and y coordinates as attributes.

Now we have a changing requirement - we want to get to make the polar
coordinates available. To keep the API consistent, they should be
another pair of attributes, r and theta. Thanks to Pythons nice
properties, we can implement these with a pair of getters, and compute
them on the fly.

But the API cannot be consistent. :) If setting r is expensive
because it requires several trig calculations but setting x is cheap,
that's an inconsistency. It would be a vital one for any application
where I'd be likely to use a point. You certainly couldn't change the
internal representation of a point from cartesian to polar without
breaking my code.

Good example from the C++ strandard library; string only specifies the
'literal' interface. The internal representation is left totally
undefined... and so you can only program to a specific implementation
of string. (Which, of course, can and does change between different
versions of a compiler, let alone between compilers.) The STL got
things right, by contrast.

Sometimes these issues don't matter much. Other times they do.
Perhaps they matter more to me because if the Python version is not
sufficiently fast, then I have to recode the thing in C++. ;-)

Anyway, my point: some types of things fundamentally cannot be cleanly
seperated into an implementation and an API. Whether a point is 2D or
polar is one of these issues, IMO.

This is obviously not to diss the whole idea of encapsulation and
modularisation. I've worked on horrible code and beautiful code, and I
know what a difference these things make. However, you also cannot
program blindly by general rules. The toughest code I've ever had to
modify would probably have passed quite a few OO-style guides; the
author was really trying to adhere to a 'philosophy', he just didn't
get it.

James M
 
J

John J. Lee

Steve Holden said:
The "Law" of Demeter isn't about *how* you access objects, it's about
what interfaces to objects you can "legally" manipulate without undue
instability across refactoring. In other words, it's about semantics,
not syntax. And it's led a lot of Java programmers down a path that
makes their programs less, not more, readable.

Not only Java programmers -- I know I've mis-applied LoD many times.
When should it (not) be applied? I don't find any inaccuracies in
'How to apply the LawOfDemeter successfully' at c2.com (link below),
but I couldn't call it a decent explanation of the problem:

http://c2.com/cgi/wiki?LawOfDemeterIsTooRestrictive


[...]


John
 
M

Mike Meyer

Antoon Pardon said:
If both writers try to implement the same kind of object I would
think the API would be very similar yes.

That's why we have one great web applications platform, right?

<mike
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,997
Messages
2,570,239
Members
46,828
Latest member
LauraCastr

Latest Threads

Top