Turkey typing 2009/09/14
Mike Malone’s thrilling response to “The case against duck typing” [1] left me feeling a bit like I didn’t fully say what I set out to. I do not intend to systematically pick holes in Mike’s arguments as that will be much more fun in person with beers. In this installment I’m going to write out some code in an imaginary language called Copperhead to illustrate what I called declared typing before and decided to call turkey typing today.
A very simple example of balking at anything non-numeric.
def what_is_rent_in_san_francisco(float salary): return 47 * salary
In such simple cases, we can cause plain Python to Do The Right Thing
by forcing the argument to be a float
and catching
ValueError
, thus suppressing strings’ desire to quack.
def what_is_rent_in_san_francisco(salary): return 47 * float(salary)
It isn’t always so easy. Duck typing raises exceptions when something fails to quack rather than when something is passed to a method in which it will fail to quack. The distinction can be important.
def something_dangerous_then_a_loop(things): poke_a_tiger_with_a_stick() for thing in things: thing.stuff()
If you’re fortunate enough to survive the tiger-poke only to be
greeted by a TypeError
when things
is not
iterable, you’re going to be pretty bummed. A duck typing
language could push such call-time errors to the beginning of the method
but I bet there’s a large amount of code in the wild that relies on
this bugquirk. A programmer could check types at the top
of the method, implement database-like transactions or reorder her code
but none of those options are particularly elegant.
def something_dangerous_then_a_loop(iterable things): poke_a_tiger_with_a_stick() for thing in things: thing.stuff()
Progress! We won’t bother with the tiger unless we know the
loop will be able to execute but this is still relying on duck typing for
the stuff
method of each thing
. Perhaps this
is okay but perhaps we want to get even more specific. I like the
syntax of the C++ STL for this, so I’ll steal it.
def something_dangerous_then_a_loop(iterable<Thing> things): poke_a_tiger_with_a_stick() for thing in things: thing.stuff()
Now we know the iterable
contains only Thing
s
and can safely call stuff
on each one. If
Doodad
s and Widget
s also implement the
stuff
method, one could block out Widget
s by
specifying the argument type as iterable<[Thing,
Doodad]>
(or just [Thing, Doodad]
for a scalar
argument).
It is not my intention to get caught up in syntax. My hope is that
I can demonstrate how useful turkey typing can be. It isn’t
a regression to static typing nor is it mutually exclusive from duck typing:
note that I use the type iterable
above which is not a type in
any Python I’ve ever used. iterable
could itself
be a duck type but, importantly, one checked at the top of the method and
without nasty isinstance
or hasattr
smells.
Turkey typing is still dynamic typing, meaning values have types but symbols do not. The type checking that happens at method call time is not an optimization but rather a shortcut. Within method bodies, ducks still have to quack.
My motivation always comes back to API design (the real definition of API [2], not what Web 2.0 means by API). Automatically generated documentation will be better if it’s generated from method signatures instead of Javadoc comments. When documentation inevitably doesn’t exist or fails to measure up, the code tells you exactly what’s expected. And when you barrel into code headfirst, the compiler can tell you what type it wants instead of just which method yor’re not providing.