Guido asked me to move it here. anyway, i might as well state my case better.
in the formal definitions of object oriented programming, objects are said to encapsulate state and behavior. behavior is largely dependent on the type of the object, but the state is important nonetheless.
for example, file objects are stateful: they can be opened for reading-only, writing-only, both, or be closed altogether. still, they are all instances of the file type.
since generic functions/multi-dispatch come to help the programmer by reducing boilerplate early type-checking code and providing a more capable dispatch mechanism -- we can't overlook the state of the object.
this early type-checking goes against the spirit of pure duck typing (which i'm fond of), but is crucial when the code has side effects. in this case, you can't just start executing the code and "hope it works", as the resources (i.e., files) involved are modified. in this kind of code, you want to check everything is alright *instead* of suddenly having an AttributeError/TypeError somewhere.
here's an example:
@dispatch def copy(src: file, dst: file): while True: buf = src.read(1000) if not buf: break dst.write(buf)
suppose now that dst is mistakenly opened for reading. this means src would have already been modified, while dst.write is bound to fail. if src is a socket, for instance, this would be destructive.
so if we already go as far as having multiple dispatch, which imposes constraints on the arguments a function accepts, we might as well base it on the type and state of the object, rather than only on it's type.
we could say, for example:
@dispatch def copy(src: file_for_reading, dst: file_for_writing):
file_for_reading is not a type -- it's a checker. it may be defined as
def file_for_reading(obj: file): return file.mode == "r" and not file.closed
types, by default, would check using isinstance(), but costume checkers could check for stateful requirements too.
and a note about performance: this check is required whether it's done explicitly or by the dispatch mechanism, and since most functions don't require so many overloads, i don't think it's an issue.
besides, we can have a different decorator for dispatching by type or by checkers, i.e., @dispatch vs @stateful_dispatch, or something. the simple @dispatch would use a dictionary, while the stateful version would use a loop.
---------- Forwarded message ---------- From: tomer filiba firstname.lastname@example.org Date: Jan 14, 2007 2:28 PM Subject: multi-dispatch again To: Pythonemail@example.com
i just thought of a so-to-speak counter-example for ABCs... it's not really a counter-example, but i believe it shows a deficiency in the concept.
theoretically speaking, objects are a made of type and state. ABCs, isinstance() and interfaces at general only check the type part. for example:
@dispatch def log_to_file(text: str, device: file): file.write(text)
this will constrain the *type* of the device, but not its *state*. practically speaking, i can pass a closed file, or a file open for reading-only, and it would pass silently.
basing multi-dispatch on types is of course a leap forward, but if we already plan to take this leap, why not make it general enough to support more complex use-cases?
this way we could rewrite the snippet above as
@dispatch def log_to_file(text: str, device: open_file): file.write(text)
where open_file isn't a type, but rather a "checker" that may also examine the state. by default, type objects would check for inheritance (via a special method), but checkers could extend this behavior.
for efficiency purposes, we can have two decorators: @type_dispatch - dispatches based on type only @full_dispatch - dispatches based on type and state
bottom line -- we can't just look at the type of the object for dispatching,
overlooking its state. the state is meaningful, and we'd want the function not to be called at all if the state of the object is wrong.