Another newbie question

S

solaris_1234

I am a python newbie and have been trying to learn python. To this
end, I have coded the following program creates:
a 8 by 8 checker board
Places two checkers on the board
Checks the board and prints out which squares has a checker on them.

It works. But I have a one question:

1) The stmt "board.Blist[10].DrawQueen(board.Blist[10].b1)" seems
awkward. Is there another way (cleaner, more intuitive) to get the
same thing done?

I appreciate any and all input on how the following program could be
improved.

from Tkinter import *
import time
totalSolutionCount = 0

class MyBox:
def __init__(self, myC, myrow, mycolumn, color):
self.b1 = Canvas(myC, background=color, width=50, height=50)
self.b1.grid(row=myrow, column=mycolumn)
self.occupied = 0

def ChangebgColor(self, box):
box.config(bg="black")

def DrawQueen(self, box):
box.item = box.create_oval(4,4,50,50,fill="black")
self.occupied = 1
box.update()

def unDrawQueen(self, box):
box.delete(box.item)
self.occupied = 0
box.update()

class MyBoard(MyBox) :
def __init__(self, myC):
self.Blist = []
count=0
for i in range(8):
count += 1
for j in range(8):
count += 1
if (count%2):
self.Blist.append(MyBox(myContainer,i,j, "red"))
else:
self.Blist.append(MyBox(myContainer,i,j, "green"))


root=Tk()
myContainer = Frame(root)
myContainer.pack()

board=MyBoard(myContainer)

board.Blist[10].DrawQueen(board.Blist[10].b1)
board.Blist[22].DrawQueen(board.Blist[22].b1)

raw_input() # A Hack debug statement

for i in range(64):
if board.Blist.occupied == 1:
print i, "is occupied"

raw_input() # A Hack debug statement
print "\n"*3
 
M

Mike Meyer

solaris_1234 said:
1) The stmt "board.Blist[10].DrawQueen(board.Blist[10].b1)" seems
awkward. Is there another way (cleaner, more intuitive) to get the
same thing done?

Yes. Reaching through objects to do things is usually a bad idea. Some
languages don't allow you to do that at all; they require you to
provide methods for manipulating the state of the object For instance,
you can extend your MyBoard class with an extra method:

def DrawQueen(self, cell):
square = self.Blist[cell]
square.DrawQueen(square.b1)


And then those two lines become:

board.DrawQueen(10)
board.DrawQueen(22)

Except that's still ugly - you probably want something like
board.DrawQueen(1, 2).

Basically, Blist should be part of MyBoards implementation, not a
visible attribute. You should define methods for MyBoard that let
clients manipulate the board, without needing to know how it's
represented internally.

Along the same lines, why does MyBoard inherit from MyBox? It's not
using any of the features of MyBox. The code still works if you don't
do that. And why do you pass instances of Cavnas to the methods of
MyBox - it's got a canvas already! Do you really expect a MyBox to
draw onto Canvases other than it's own (if so, that's a bad design as
well).

Here's an updated version of your code. I've used the convention of an
_ prefix on attributes to indicate implementation details, and made
the classes inherit from object, as well as using "box" instead of
"b1", and changed the interface to MyBoard squares to use standard
2d-array indexing instead of forcing the clients to do array
index calculations. You may have a good reason for doing these things
that doesn't appear in your code fragment, but I consider these to be
improvements in the fragment.

Hmm. "b1" seems to indicate that you will eventually have more than
one canvas, which is why you passed in the canvas? In which case, the
distinguishing feature would be the number (or mabye the "b1"). In
that case, have your clients pass in the number (or name), and look up
the canvas in an internal structure.

<mike

from Tkinter import *
import time
totalSolutionCount = 0

class MyBox(object):
def __init__(self, myC, myrow, mycolumn, color):
self._box = Canvas(myC, background=color, width=50, height=50)
self._box.grid(row=myrow, column=mycolumn)
self.occupied = False

def ChangebgColor(self):
self._box.config(bg="black")

def DrawQueen(self):
self._box.item = self._box.create_oval(4,4,50,50,fill="black")
self.occupied = True
self._box.update()

def unDrawQueen(self):
self._box.delete(self._box.item)
self.occupied = False
self._box.update()

class MyBoard(object) :
def __init__(self, myC):
self._blist = {}
for i in range(8):
for j in range(8):
self._blist[i, j] = MyBox(myContainer, i, j,
("green", "red")[(i * 8 + j) % 2])
def DrawQueen(self, i, j):
square = self._blist[i, j]
square.DrawQueen()

def occupied(self, i, j):
return self._blist[i, j].occupied


root=Tk()
myContainer = Frame(root)
myContainer.pack()

board=MyBoard(myContainer)

board.DrawQueen(1, 2)
board.DrawQueen(2, 6)

raw_input() # A Hack debug statement

for i in range(8):
for j in range(8):
if board.occupied(i, j):
print "%d, %d is occupied" % (i, j)

raw_input() # A Hack debug statement
print "\n"*3
 
S

solaris_1234

Mike,

Thanks for your insight. It has been a big help.

I guess I was trying to learn too much with my original code. Trying to
implement inheritance, object creation, calling methods via inheritance
made the code harder than it needed to be.

I'm off to study the code. (Hmm.. how does python parse ("green",
"red")[(i * 8 + j) % 2] command ... he says while reaching for "python
for the semi-illiterate" ...)

Again, thanks for your help.


Jpl


Mike said:
solaris_1234 said:
1) The stmt "board.Blist[10].DrawQueen(board.Blist[10].b1)" seems
awkward. Is there another way (cleaner, more intuitive) to get the
same thing done?

Yes. Reaching through objects to do things is usually a bad idea. Some
languages don't allow you to do that at all; they require you to
provide methods for manipulating the state of the object For instance,
you can extend your MyBoard class with an extra method:

def DrawQueen(self, cell):
square = self.Blist[cell]
square.DrawQueen(square.b1)


And then those two lines become:

board.DrawQueen(10)
board.DrawQueen(22)

Except that's still ugly - you probably want something like
board.DrawQueen(1, 2).

Basically, Blist should be part of MyBoards implementation, not a
visible attribute. You should define methods for MyBoard that let
clients manipulate the board, without needing to know how it's
represented internally.

Along the same lines, why does MyBoard inherit from MyBox? It's not
using any of the features of MyBox. The code still works if you don't
do that. And why do you pass instances of Cavnas to the methods of
MyBox - it's got a canvas already! Do you really expect a MyBox to
draw onto Canvases other than it's own (if so, that's a bad design as
well).

Here's an updated version of your code. I've used the convention of an
_ prefix on attributes to indicate implementation details, and made
the classes inherit from object, as well as using "box" instead of
"b1", and changed the interface to MyBoard squares to use standard
2d-array indexing instead of forcing the clients to do array
index calculations. You may have a good reason for doing these things
that doesn't appear in your code fragment, but I consider these to be
improvements in the fragment.

Hmm. "b1" seems to indicate that you will eventually have more than
one canvas, which is why you passed in the canvas? In which case, the
distinguishing feature would be the number (or mabye the "b1"). In
that case, have your clients pass in the number (or name), and look up
the canvas in an internal structure.

<mike

from Tkinter import *
import time
totalSolutionCount = 0

class MyBox(object):
def __init__(self, myC, myrow, mycolumn, color):
self._box = Canvas(myC, background=color, width=50, height=50)
self._box.grid(row=myrow, column=mycolumn)
self.occupied = False

def ChangebgColor(self):
self._box.config(bg="black")

def DrawQueen(self):
self._box.item = self._box.create_oval(4,4,50,50,fill="black")
self.occupied = True
self._box.update()

def unDrawQueen(self):
self._box.delete(self._box.item)
self.occupied = False
self._box.update()

class MyBoard(object) :
def __init__(self, myC):
self._blist = {}
for i in range(8):
for j in range(8):
self._blist[i, j] = MyBox(myContainer, i, j,
("green", "red")[(i * 8 + j) % 2])
def DrawQueen(self, i, j):
square = self._blist[i, j]
square.DrawQueen()

def occupied(self, i, j):
return self._blist[i, j].occupied


root=Tk()
myContainer = Frame(root)
myContainer.pack()

board=MyBoard(myContainer)

board.DrawQueen(1, 2)
board.DrawQueen(2, 6)

raw_input() # A Hack debug statement

for i in range(8):
for j in range(8):
if board.occupied(i, j):
print "%d, %d is occupied" % (i, j)

raw_input() # A Hack debug statement
print "\n"*3
 
J

John Bushnell

I think that's supposed to be [(i + j) % 2] for the index to the
("green","red") tuple
(since i*8 is always even).

Mike,

Thanks for your insight. It has been a big help.

I guess I was trying to learn too much with my original code. Trying to
implement inheritance, object creation, calling methods via inheritance
made the code harder than it needed to be.

I'm off to study the code. (Hmm.. how does python parse ("green",
"red")[(i * 8 + j) % 2] command ... he says while reaching for "python
for the semi-illiterate" ...)

Again, thanks for your help.


Jpl


Mike said:
solaris_1234 said:
1) The stmt "board.Blist[10].DrawQueen(board.Blist[10].b1)" seems
awkward. Is there another way (cleaner, more intuitive) to get the
same thing done?

Yes. Reaching through objects to do things is usually a bad idea. Some
languages don't allow you to do that at all; they require you to
provide methods for manipulating the state of the object For instance,
you can extend your MyBoard class with an extra method:

def DrawQueen(self, cell):
square = self.Blist[cell]
square.DrawQueen(square.b1)


And then those two lines become:

board.DrawQueen(10)
board.DrawQueen(22)

Except that's still ugly - you probably want something like
board.DrawQueen(1, 2).

Basically, Blist should be part of MyBoards implementation, not a
visible attribute. You should define methods for MyBoard that let
clients manipulate the board, without needing to know how it's
represented internally.

Along the same lines, why does MyBoard inherit from MyBox? It's not
using any of the features of MyBox. The code still works if you don't
do that. And why do you pass instances of Cavnas to the methods of
MyBox - it's got a canvas already! Do you really expect a MyBox to
draw onto Canvases other than it's own (if so, that's a bad design as
well).

Here's an updated version of your code. I've used the convention of an
_ prefix on attributes to indicate implementation details, and made
the classes inherit from object, as well as using "box" instead of
"b1", and changed the interface to MyBoard squares to use standard
2d-array indexing instead of forcing the clients to do array
index calculations. You may have a good reason for doing these things
that doesn't appear in your code fragment, but I consider these to be
improvements in the fragment.

Hmm. "b1" seems to indicate that you will eventually have more than
one canvas, which is why you passed in the canvas? In which case, the
distinguishing feature would be the number (or mabye the "b1"). In
that case, have your clients pass in the number (or name), and look up
the canvas in an internal structure.

<mike

from Tkinter import *
import time
totalSolutionCount = 0

class MyBox(object):
def __init__(self, myC, myrow, mycolumn, color):
self._box = Canvas(myC, background=color, width=50, height=50)
self._box.grid(row=myrow, column=mycolumn)
self.occupied = False

def ChangebgColor(self):
self._box.config(bg="black")

def DrawQueen(self):
self._box.item = self._box.create_oval(4,4,50,50,fill="black")
self.occupied = True
self._box.update()

def unDrawQueen(self):
self._box.delete(self._box.item)
self.occupied = False
self._box.update()

class MyBoard(object) :
def __init__(self, myC):
self._blist = {}
for i in range(8):
for j in range(8):
self._blist[i, j] = MyBox(myContainer, i, j,
("green", "red")[(i * 8 + j) % 2])
def DrawQueen(self, i, j):
square = self._blist[i, j]
square.DrawQueen()

def occupied(self, i, j):
return self._blist[i, j].occupied


root=Tk()
myContainer = Frame(root)
myContainer.pack()

board=MyBoard(myContainer)

board.DrawQueen(1, 2)
board.DrawQueen(2, 6)

raw_input() # A Hack debug statement

for i in range(8):
for j in range(8):
if board.occupied(i, j):
print "%d, %d is occupied" % (i, j)

raw_input() # A Hack debug statement
print "\n"*3
 
S

Steven D'Aprano

solaris_1234 said:
1) The stmt "board.Blist[10].DrawQueen(board.Blist[10].b1)" seems
awkward. Is there another way (cleaner, more intuitive) to get the
same thing done?

Yes. Reaching through objects to do things is usually a bad idea.

I don't necessarily disagree, but I don't understand why you say this. Why
it is bad?
Some languages don't allow you to do that at all;

Fortunately we aren't using "some languages", we're using Python, and so
we aren't forced to fill our classes with helper functions when we can
simply call the object methods directly.
 
P

Paul Rubin

Steven D'Aprano said:
I don't necessarily disagree, but I don't understand why you say this. Why
it is bad?

The traditional OOP spirit is to encapsulate the object's entire
behavior in the class definition.
 
M

Mike Meyer

Steven D'Aprano said:
solaris_1234 said:
1) The stmt "board.Blist[10].DrawQueen(board.Blist[10].b1)" seems
awkward. Is there another way (cleaner, more intuitive) to get the
same thing done?
Yes. Reaching through objects to do things is usually a bad idea.
I don't necessarily disagree, but I don't understand why you say this. Why
it is bad?

Such behavior couples you to the objects you use very tightly. This
makes it harder to adapt those objects to changing needs. One popular
rule of thumb is the "Law of Demeter". Googling for that will turn up
lots of information.

My standard object interface is modeled after Meyer's presentation in
OOSC: an objects state is manipulated with methods and examined with
attributes; manipulating attributes doesn't change the internal state
of the object. This makes it possible to change the internal
representation of a class without having to change all the clients of
the class to match.
Fortunately we aren't using "some languages", we're using Python, and so
we aren't forced to fill our classes with helper functions when we can
simply call the object methods directly.

Yup. I'm not pushing for a change in Python to suit my design
goals. If I feel the need for languages that enforce my design
decisions, I know where to find them.

<mike
 
S

Steven D'Aprano

Paul said:
The traditional OOP spirit is to encapsulate the object's entire
behavior in the class definition.


Uh huh. Say I have:

class Coordinate:
def __init__(self, x=0.0, y=0.0):
self.x = x
self.y = y

pt = Coordinate(1.0, 2.5)

Then, somewhere in my application, I need twice the
value of the y ordinate. I would simply say:

value = 2*pt.y

But you claim that this is bad practice, and to be true
to the OOP spirit I should encapsulate the objects
behaviour by creating a method like this:

# add this to the class definition
def mult_y_ord(self, other):
"Return the y ordinate multiplied by other."
return other*self.y

Presumably then I also need add_y_ord, sub_y_ord,
rsub_y_ord, div_y_ord, and so on for every method that
floats understand, plus *another* set of methods that
do the same thing for the x ordinate. And in every
case, these Coordinate methods are trivial one-liners.

Do people really do this?

Yes, I could encapsulate the lot with a factory
function that applied a specified operator to a
specified attribute, and populate the class at runtime.
But why would I want to?

Now, as I see it, the whole point of encapsulation is
that you *don't* need to fill your class definition
with meaningless helper functions. If an attribute of a
instance is a float, you can just call float methods on
the attribute and it should work. If the attribute is a
list, list methods will work. If the attribute is an
instance of a custom class, the same general technique
will still work.
 
B

Bruno Desthuilliers

(e-mail address removed) a écrit :
I'm off to study the code. (Hmm.. how does python parse ("green",
"red")[(i * 8 + j) % 2] command ...

("green", "red")[0] == "green"
("green", "red")[1] == "red"

(i * 8 + j) is somewhat trivial (just take care of precedence order),
and will return an integer
% is the modulo operator.
the modulo 2 of any integer x is 0 if x is even and 1 if x is odd
(that's in fact the reversed definition !-)

So this expression[1] will return "green" if (i * 8 + j) is pair and
"red" if (i * 8 + j) is even.

Using computed indexed access for dispatch is a common python idiom.
Instead of:

if condition:
result = iftrue
else:
result = iffalse

you can simply write:
result = (iffalse, iftrue)[condition]


[1] 'expression', not 'command'. An expression has a value and can be
used on the right hand side of the assignment operator.
 
M

Mike Meyer

Steven D'Aprano said:
Uh huh. Say I have:

class Coordinate:
def __init__(self, x=0.0, y=0.0):
self.x = x
self.y = y

pt = Coordinate(1.0, 2.5)
Presumably then I also need add_y_ord, sub_y_ord, rsub_y_ord,
div_y_ord, and so on for every method that floats understand, plus
*another* set of methods that do the same thing for the x
ordinate. And in every case, these Coordinate methods are trivial
one-liners.
Do people really do this?

Yes, but usually with better API design. For instance, your Coordinate
class might have scale, translate, rotate and move methods. These are
still relatively simple, but they aren't one-liners. The import thing
is that these methods capture common, Coordinate-level operations and
bundle them up so that clients don't have to, for instance, do the
trig needed to rotate a point themselves. They can just use the rotate
method.
Yes, I could encapsulate the lot with a factory function that applied
a specified operator to a specified attribute, and populate the class
at runtime. But why would I want to?

You don't. You're thinking about things at the wrong level. You don't
want to think about "things you do to a Coordinate's attribute". You
want to think about "things you do to a Coordinate".
Now, as I see it, the whole point of encapsulation is that you *don't*
need to fill your class definition with meaningless helper
functions.

Correct. That's where you went wrong - your methods were essentially
meaningless. They just manipulated the attributes, not the
Coordinate. Your methods should be meaningful for the object, not just
the attributes.
If an attribute of a instance is a float, you can just call
float methods on the attribute and it should work. If the attribute is
a list, list methods will work. If the attribute is an instance of a
custom class, the same general technique will still work.

So, if we expand your Coordinate class to have attriutes r and theta
(the same coordinate expressed in polar form), which are also floats,
does it make sense to write: pt.r *= 2? For that matter, does it
*still* make sense to write pt.x *= x?

Ok, my design philosophy is that you don't do those things. However,
Python is powerful enough that you can do those things and have them
work right. You just have to make x, y, theta and r properties, so you
can run code when someone sets or reads them.

And finally, there are times when your class is really just a
convenient way to bundle data together (because it's easier to write
"pt.x" than pt['x']), so there really aren't any applicable high-level
methods. In that case, all you do is tweak the attributes; you might
as well do it directly.

<mike
 
A

Alex Martelli

Mike Meyer said:
My standard object interface is modeled after Meyer's presentation in
OOSC: an objects state is manipulated with methods and examined with
attributes; manipulating attributes doesn't change the internal state
of the object. This makes it possible to change the internal
representation of a class without having to change all the clients of
the class to match.

Note that properties enable you to obtain the goal in your final
sentence while letting attributes still be freely assigned -- a vastly
preferable solution. As far as I recall, Meyer's Eiffel allows this
syntactic transparency for accessing attributes (since it does not
require parentheses in function calls) but not for setting them; so you
might end up writing boilerplate setThis, setThat, and so on, although
at least you're spared the half of the boilerplate that goes getThis,
getThat in typical Java...


Alex
 
M

Mike Meyer

Note that properties enable you to obtain the goal in your final
sentence while letting attributes still be freely assigned -- a vastly
preferable solution. As far as I recall, Meyer's Eiffel allows this
syntactic transparency for accessing attributes (since it does not
require parentheses in function calls) but not for setting them; so you
might end up writing boilerplate setThis, setThat, and so on, although
at least you're spared the half of the boilerplate that goes getThis,
getThat in typical Java...

Yup, properties are powerful things - I was very happy to see them
added to Python. And Eiffel indeed gets the same transparency by
allowing obj.feature to be a parameterless method invocation (without
having to go through the dance required to create a property), an
attribute reference, or a reference to a computed constant (a "once"
feature).

On the other hand, Eiffel specifically forbids setting attributes
directly, except for the object they belong to. This is done to
enforce the design goals I stated above: attributes are the "readouts"
for an object, and methods are the knobs/dials/etc. This also ties in
with the compiler having facilities to check class invariants. If you
allow assignments to attributes in other classes, the assignments have
to generate code to check the invariants every time you have such an
assignment, otherwise a future attribute read may come from an object
in an invalid state. If you only allow attributes to be set by the
owning objects methods, then you only have to check the invariants on
method exit.

Since I like that object model, this restriction doesn't bother me. On
the other hand, I don't find myself writing set_* boilerplate very
often - my methods tend to be written to manipulate the class rather
than the attributes. Most of the time I write such methods, it's
because Eiffel doesn't have default values for arguments. So what I'd
write in Python as:

obj = My_class(1, foo = 23)

I write in Eiffel as:

obj = MY_CLASS(1)
obj.set_foo(23)

Different idioms for different languages. Trying to write Python in
Eiffel doesn't work any better than trying to write C++ in Python.

<mike
 
A

Alex Martelli

Mike Meyer said:
...
On the other hand, Eiffel specifically forbids setting attributes
directly, except for the object they belong to. This is done to
enforce the design goals I stated above: attributes are the "readouts"

We must have a different meaning in mind for the word "goal"; I take it
to mean, as a web definition says, "the state of affairs that a plan is
intended to achieve". By this definition, your second quoted sentence
identifies a goal, but the former one doesn't -- it's a MEANS, not an
END, as strongly confirmed by the words "This makes it possible to...".

Enabling internals' changes is a goal, and an important one; doing so by
forbidding the setting of attributes is one way to help achieve it, but
properties are another way, and as a matter of language design it
appears to me to be a better approach. I haven't used Eiffel "in
anger", just evaluated and rejected it years ago for an employer, but I
have used other languages such as ObjectiveC which follow a similar
approach, so my preference is based on some experience (Ruby, though
strongly influenced by Smalltalk, does let you define attribute-setter
methods, with a net effect similar to Deplhi's or Python's properties).
for an object, and methods are the knobs/dials/etc. This also ties in
with the compiler having facilities to check class invariants. If you
allow assignments to attributes in other classes, the assignments have
to generate code to check the invariants every time you have such an
assignment, otherwise a future attribute read may come from an object
in an invalid state. If you only allow attributes to be set by the
owning objects methods, then you only have to check the invariants on
method exit.

What classes' invariants do you have to check in those cases? E.g.,
consider zim.foo.bar.baz() -- you do have to check the invariants of
bar, foo AND zim, right? And you must do it with code placed inline
after this specific call, since not all calls to that baz method must
check invariants in foo and zim. So, what's different about having to
generate just the same inline code for, say, zim.foo.bar=baz ? Since
your compiler must be ready to generate such checks anyway, forbidding
the second form appears to have no pluses. Or, in other words, having
to spell zim.foo.bar=baz as zim.foo.set_bar(baz) [[and having to code
boilerplate to implement set_bar]] is a style-choice (common to Eiffel
and Smalltalk, and others) which I consider inferior to the alternative
choice you find in Ruby or Python [[allowing autogeneration of the
implied attribute-setter method if necessary]].
Different idioms for different languages. Trying to write Python in
Eiffel doesn't work any better than trying to write C++ in Python.

Absolutely, but here I was discussing language design, not usage of
languages whose design is an external "given".


Alex
 
M

Mike Meyer

What classes' invariants do you have to check in those cases? E.g.,
consider zim.foo.bar.baz() -- you do have to check the invariants of
bar, foo AND zim, right?

Nope, just bar. Attributes display state, they don't let you change
it. Nothing you do with zim.foo or zim.foo.bar can change the state of
zim. The only invariants you need to check are bar's, which you do at
the exit to it's baz method.
Or, in other words, having
to spell zim.foo.bar=baz as zim.foo.set_bar(baz) [[and having to code
boilerplate to implement set_bar]] is a style-choice (common to Eiffel
and Smalltalk, and others) which I consider inferior to the alternative
choice you find in Ruby or Python [[allowing autogeneration of the
implied attribute-setter method if necessary]].

It's a style choice derived from the designers believe about what
constitutes "good design". It's not really any different from python
requiring you to spell fetch_tuple().index(val) as
list(fetch_tuple()).index(val): the language designers have decided
what's "proper" usage of some element of the language, and the
features of the language support such usage, and not other usage.

You may not agree with what Meyer believes - in which case you would
be right to reject any language he designed.

<mike
 
A

Alex Martelli

Mike Meyer said:
Nope, just bar. Attributes display state, they don't let you change
it. Nothing you do with zim.foo or zim.foo.bar can change the state of
zim. The only invariants you need to check are bar's, which you do at
the exit to it's baz method.

So foo's class is not allowed to have as its invariant any formula
depending on the attributes of its attribute bar, such as "bar.x>23" or
the like? Wow! How does Eiffel enforce this prohibition? Forbidding
access to any attribute's attribute in an invariant? I sure don't
remember that from my study of Eiffel, but admittedly that was in the
past. I'm also quite dubious as to how you can then express some
invariants that can be very important, but before I delve into that I
would ask you to confirm that there's a prohibition of access to
attributes' attributes in an invariant (ideally with some URLs,
thanks!).
You may not agree with what Meyer believes - in which case you would
be right to reject any language he designed.

Of course I don't agree with such absurd statements of his as the fact
that you can have OO only with static typing, which would rule out
Smalltalk (for example!) from the set of OO languages; I'm hard put to
see how anybody could _agree_ with them, in fact. Further, I believe
static typing is appropriate only in a functional language (unmodifiable
data), and untenable, as a matter of principle, in languages which let
you alter data on the fly; the IS-A relationship conjoined with
modifiable data is just too strict (e.g., you cannot state "a Circle
IS-A Ellipse" if modification exists and Ellipse has been reasonably
designed with methods set_x and set_y whose postconditions include the
fact that they modify ONLY the named axis; you typically want
covariance, but that's mathematically unsound, as it would violate
Liskov's substition principle; etc, etc) so you inevitably end up with
runtime checks instead (so that the stance of "things must be checked at
compile time" gets broken -- but then, the whole concept of contract
programming implies lots of *runtime* checks, anyway). Still, if I
_had_ to use a statically typed OO language without being allowed to
pick a functional one, I guess I would not consider Eiffel (as a
language, net of any practical issues of implementation) necessarily
worse than, say, C++ or Java, which have their own problems.

Fortunately, for most of my work, I do get to use Python, Objective-C,
or Haskell, which, albeit in very different ways, are all "purer" (able
to stick to their principles)...


Alex
 
M

Mike Meyer

So foo's class is not allowed to have as its invariant any formula
depending on the attributes of its attribute bar, such as "bar.x>23" or
the like?

Of course you can do such things. But it's a silly thing to do. That
invariant should be written as x > 23 for the class bar is an instance
of. Invariants are intended to be used to check the state of the
class, not the state of arbitary other objects. Doing the latter
requires that you have to check the invariants of every object pretty
much every time anything changes.

Invariants are a tool. Used wisely, they make finding and fixing some
logic bugs much easier than it would be otherwise. Used unwisely, they
don't do anything but make the code bigger.
I'm also quite dubious as to how you can then express some
invariants that can be very important

Not all invariants, pre-conditions or post-conditions can be
expressed.
Fortunately, for most of my work, I do get to use Python, Objective-C,
or Haskell, which, albeit in very different ways, are all "purer" (able
to stick to their principles)...

I think Eiffel is fairly pure. But practicality beats purity, so there
are places where it has to give in and deviate from it's
principles. Clearly, you don't agree with the underlying
philosoiphy. So don't use it.

<mike
 
S

Steven D'Aprano

Yes, but usually with better API design. For instance, your Coordinate
class might have scale, translate, rotate and move methods.

Which I obviously left as exercises for the reader. Did I really need to
specify the entire API for an example like this?

These are
still relatively simple, but they aren't one-liners. The import thing
is that these methods capture common, Coordinate-level operations and
bundle them up so that clients don't have to, for instance, do the
trig needed to rotate a point themselves. They can just use the rotate
method.

Of course, if you have to do an operation on a *coordinate* then it makes
perfect sense to create coordinate methods. I'm not disputing that. But in
my example, I'm not doing things to a coordinate, I'm doing things to the
entities which make up a coordinate -- the x and y ordinates.

You don't. You're thinking about things at the wrong level. You don't
want to think about "things you do to a Coordinate's attribute". You
want to think about "things you do to a Coordinate".

I've done my thinking about coordinates. That is why I wrote a Coordinate
class. But sometimes you don't want to do an operation on a coordinate, you
want to do something to the x or y ordinate alone. It is utter nonsense to
suggest that you should abstract coordinates to the point that you no
longer know -- or pretend that you don't -- that a coordinate is a pair of
ordinates. What, we're supposed to guard against the possibility that
coordinates might be implemented by a B-tree or something?

(Yes, I'm aware there are 3D coordinates, or even n-dimensional ones, and
all sorts of special purpose coordinates with, e.g. ordinates limited to
integral values. I'm not writing a general anything-coordinate class, I'm
writing a simple 2D coordinate pair class.)

Correct. That's where you went wrong - your methods were essentially
meaningless. They just manipulated the attributes, not the Coordinate.
Your methods should be meaningful for the object, not just the
attributes.

*Exactly* my point -- and demonstrating that you've missed that point.
Writing special purpose methods to manipulate object attributes when you
can just as easily manipulate the object attributes is a bad idea. Methods
should be meaningful for the object.

According to the Law of Demeter (more of a guideline really), each level
should only talk to the immediate level next to it. Fine: I have a name
"pt", which is bound to a Coordinate object -- so "pt" can call Coordinate
methods. But it shouldn't call float methods on the attributes of that
Coordinate object, because those float methods are two levels away.

The bad side of the Guideline of Demeter is that following it requires
you to fill your class with trivial, unnecessary getter and setter
methods, plus methods for arithmetic operations, and so on.

Or just say No to the "law" of Demeter.

As a guideline, to make you think about what your doing ("am I doing too
much work? should my class implement a helper function for this common
task?") it is perfectly fine. But when you find yourself writing trivial
methods that do nothing but call methods on a attribute, you are another
victim of a bad law.

So, if we expand your Coordinate class to have attriutes r and theta
(the same coordinate expressed in polar form), which are also floats,
does it make sense to write: pt.r *= 2?

If I expand the Coordinate class to allow both polar coordinates and
Cartesian coordinates, I need some mechanism for keeping the two views in
sync. If I can do that (say, with properties) then sure it makes sense to
double the length of the coordinate vector.

If I *can't* keep the two views in sync, then I have some choices to make.
For instance, I might have a Cartesian class and a Polar class, with
methods to convert from one to the other, and give up on the desire for
one object to encapsulate both. My Coordinate class doesn't encapsulate
writing a point in English "one point two, three point four five"
either -- you shouldn't expect a single class to encapsulate every
imaginable representation of that class ("but what if I want to write my
binary tree in Morse code?").
 
M

Mike Meyer

Steven D'Aprano said:
Which I obviously left as exercises for the reader. Did I really need to
specify the entire API for an example like this?

Given that we're talking about API design, then yes, you do. With only
partial examples, you'll only get partial conclusions. 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).
I've done my thinking about coordinates. That is why I wrote a Coordinate
class. But sometimes you don't want to do an operation on a coordinate, you
want to do something to the x or y ordinate alone. It is utter nonsense to
suggest that you should abstract coordinates to the point that you no
longer know -- or pretend that you don't -- that a coordinate is a pair of
ordinates.

Which demonstrates that *you've* missed the point. It's not utter
nonsense to abstract coordinates - or any other class - to the point
that you don't know what the underlying implementation is. That's what
abstraction is all about. Yup, a cordinate has a pair of ordinates. So
you have attributes to get them.
*Exactly* my point -- and demonstrating that you've missed that point.
Writing special purpose methods to manipulate object attributes when you
can just as easily manipulate the object attributes is a bad idea. Methods
should be meaningful for the object.

And you've once again missed the point. The reason you don't
manipulate the attributes directly is because it violates
encapsulation, and tightens the coupling between your class and the
classes it uses. It means you see the implementation details of the
classes you are using, meaning that if that changes, your class has to
be changed to match.
The bad side of the Guideline of Demeter is that following it requires
you to fill your class with trivial, unnecessary getter and setter
methods, plus methods for arithmetic operations, and so on.

No, it requires you to actually *think* about your API, instead of
just allowing every class to poke around inside your implementation.
As a guideline, to make you think about what your doing ("am I doing too
much work? should my class implement a helper function for this common
task?") it is perfectly fine. But when you find yourself writing trivial
methods that do nothing but call methods on a attribute, you are another
victim of a bad law.

More likely, you're just committing a bad design.
If I *can't* keep the two views in sync, then I have some choices to make.
For instance, I might have a Cartesian class and a Polar class, with
methods to c
onvert from one to the other, and give up on the desire for
one object to encapsulate both.

Those are all valid choices. They have their own tradeoffs. Following
the LoD lowers the coupling between classes, making it easier to
change/extend/etc. them; on the down side, you have to take more care
in your API design, and some operations take a bit more typing. You
may not like that trade off. That's fine; I've already mentioned some
cases where it makes sense to do otherwise. Don't do things that way -
but do know what the tradeoffs are.

<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

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,901
Latest member
Noble71S45

Latest Threads

Top