From: (e-mail address removed)
Subject: Re: duck typing assert
Date: Fri, 9 Nov 2012 13:36:39 +0000
To: (e-mail address removed)
I disagree. Does that test whether Foo looks like IFoo, or IFoo looks
like Foo?
What's the difference? "Looks like" is a symmetric comparison, like
"equal" and "almost equal", but not "subset", "less than" etc. If x looks
like y, then necessarily y must look like x. If Katy Perry looks like
Zooey Deschanel, then it stands to reason that Zooey Deschanel looks like
Katy Perry. James Woods looks like Erwin Schroedinger, and Erwin
Schroedinger looks like James Woods.
http://tvrefill.com/wp-content/uploads/2010/12/zooey-deschanel.jpg
http://cheezburger.com/6704400128
So in that sense, looks(Spam).like(Ham) must always be the same as
looks(Ham).like(Spam), and the order of operators doesn't matter.
But that's not what we want! And to his credit, that's not what Andriy
Kornatskyy's code actually implements. The problem is with the name,
which is actively misleading by suggesting a symmetrical comparison for
one which is not symmetrical.
Suppose we want to make a movie with Zooey Deschanel, but she's not
available, so we want a replacement who is duck-type compatible with her.
The replacement doesn't need to be a Deschanel sister, but she does need
to do *at least* everything Zooey can do. If she can do more, that's
fine, but she can't do less.
Since Katy Perry can do everything Zooey can do, PLUS she sings, we can
replace Zooey with Katy: Katy is duck-type compatible with Zooey. But we
can't replace Katy with Zooey, because Zooey can't sing.[1]
(I think... I really don't actually know if Zooey Deschanel can sing or
not. Just go along with the example.)
The point I am making is that "looks like" is not a good description for
this function. "Looks like" must be symmetrical, but the function we
actually want is not symmetrical. It is actually a "subset" type
relationship: given "looks(Zooey).like(Katy)", it checks that the public
methods etc. of Zooey are a subset of the methods of Katy. That is, that
instances of Katy can be used instead of instances of Zooey.
Or is it the other way? Damned if I know. Let's find out:
class Zooey:
def act(self): pass
class Katy:
def act(self): pass
def sing(self): pass
py> looks(Zooey).like(Katy)
__main__:2: UserWarning: 'sing': is missing.
False
I guessed wrong. The looks.like method as implemented tests that the
right-hand size operand is a subset of the right-hand-side:
py> looks(Katy).like(Zooey)
True
I argue that this is the wrong way around. (Even if it isn't the wrong
way around, it certainly isn't clear or obvious which way you have to
write the operands!)
Consider this use-case:
candidates = [Hilary, Jennifer, Katy]
expected = looks(Zooey) # instantiate the looks class once only
for actor in candidates:
if expected.like(actor):
make_movie_with(actor())
That's much nicer and more efficient than the way you have to write it
now:
candidates = [Hilary, Jennifer, Katy]
for actor in candidates:
# instantiate the looks class every time we want to test another class
if looks(actor).like(Zooey):
make_movie_with(actor())
So... it's a cute name, that sounds English-like. But it doesn't actually
describe what the function does, it is wasteful for at least one useful
use-case, and it's not clear which order you have to supply the two
arguments.
looks(Foo).like(IFoo), on the other hand, is crystal clear about which
argument is which.
I hope that by now you can see why I say that it is as clear as mud.
[1] Some people might argue that neither can Katy Perry.