Goto is a shunned construct in modern programming languages, but occasionally there is a case where it makes sense, such as breaking-out from a set of nested for
statements when a solution is found. But, modern programming languages contain a better construct for such cases---exceptions. For example, say we are trying to find an item from each of three sets which jointly satisfy some criterion. A naive implementation might look like:
But, this code can be simplified by introducing an exception:
foundMatch = False
for item1 in set1:
for item2 in set2:
for item3 in set3:
if satisfiesCriterion(item1, item2, item3):
foundMatch = True
break
if foundMatch:
break
if foundMatch:
break
try:
for item1 in set1:
for item2 in set2:
for item3 in set3:
if satisfiesCriterion(item1, item2, item3):
raise FoundMatch()
except FoundMatch:
pass
Wednesday, January 26, 2011
Using exceptions for goto
Thursday, January 20, 2011
Twisted Documentation
There is currently much discussion on the twisted mailing list about improving twisted documentation. I'm one of many who think the documentation could be improved. I found a major problem to be a lack of introduction to the twisted mental model---the fact that it uses cooperative timesharing and blocking calls to handle events.
Victor Norman suggested Dave Peticolas' Twisted Introduction. Reading the first article which explains the Twisted "mental model" felt like a breath of fresh air. I disagree with his use of asynchronous, which implies parallel, non-blocking, etc. But, starting with the mental model is definitely the right approach. Now, if only this documentation could be integrated with the main documentation...
P.S. Dave Peticolas---I've heard that name before. Sure enough, he worked on GnuCash, my accounting program of choice.
Wednesday, January 12, 2011
Twisted: callWhenRunning, callFromThread or callLater?
When I first learned of reactor.callWhenRunning
, I apparently didn't read the documentation and/or source code sufficiently carefully. I correctly understood that it was the function to use when you wanted to queue a function to be called immediately after reactor start. My mistake was to believe that it queued the function if the reactor had already been started. In fact, if the reactor is in the "running" state, it simply calls the specified function. I wonder if part of the reason for this design is how it handles the not-running case. If the reactor is not running, callWhenRunning
adds a startup trigger for the specified function. Such a trigger cannot be used to queue-up a task/call.
I learned (the hard way) of the need for callFromThread
when trying to run a web server and twisted reactor in separate threads of the same process ("don't try this at home"). Jean-Paul's answer to my question about reactor.wakeUp
provides the reason for this requirement. The reactor must make blocking calls (e.g. select()
) for certain functionality (e.g. networking). The wakeUp
trips the blocking call by, e.g., "writ[ing] a byte to a pipe the reactor is select()ing (etc) on". In my case, I found that an attempt by the web server code to write to the network might be ignored indefinitely unless the call was wrapped with callFromThread
. What does callFromThread
do? It adds the function to the threadCallQueue
and "wakes up" the reactor. Unlike callWhenRunning
the specified function call isn't made until after callFromThread
returns, so it can be used to queue-up a function for running when the reactor (re-)gains control.
If you read the callFromThread
documentation, you'll find that callLater
is the recommended way (with delay=0) to queue a function for calling in the next mainLoop
iteration. Like callFromThread
, callLater
uses a queue(s) to manage the calls. Two queues are kept: one for calls which haven't waited long enough (_newTimedCalls
), and one for calls which have waited long enough, but haven't been called yet (_pendingTimedCalls
). The _pendingTimedCalls
are called during the next mainLoop
iteration.