tag:blogger.com,1999:blog-42962688591647675872023-11-15T15:10:10.125+00:00__init__(self, blog):Elegance is not a dispensable luxury but a quality that decides between success and failure - Edsger W. DijkstraUnknownnoreply@blogger.comBlogger7125tag:blogger.com,1999:blog-4296268859164767587.post-10964270375735102432008-11-16T21:00:00.002+00:002008-11-16T21:02:44.163+00:00Another day, another blog.....I have another blog over at wordpress now; a nice light theme to contrast with this one. :)Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-4296268859164767587.post-84112959872055352812008-04-22T12:06:00.008+00:002008-05-30T01:11:55.118+00:00Python HomeworkAt <a href="http://sdk.bt.com/">work</a>, <a href="http://www.python.org/">Python</a> is one of many languages we utilize. In fact, Python is one of the APIs we offer for accessing <a href="http://web21c.bt.com/services">our services</a>. <p>We generate our Python SDK using our (sort of) open source SDK generation system, which is hosted by our friends at <a href="http://www.osmosoft.com/">osmosoft</a>. You can find the SDK generation code <a href="https://trac.osmosoft.com/Web21CSDK/browser/trunk/generation">here</a>.
</p>Anyway, there has been much debate about langauge proliferation within our group. As one of the most enthusiastic exponents of a <a href="http://memeagora.blogspot.com/2006/12/polyglot-programming.html">polyglot programming</a>, I perform quite a bit of evangelism within our group. Most recently, I've started the work of setting up a <a href="http://www.python.org/">Python</a> focus group, and in the course of doing so, have begun setting some "homework" for some of my keen fellow engineers.<p>Always ahead of the game, <a href="http://www.kerrybuckley.org/">Kerry</a> has already started blogging about some of these exploits. The second exercise is an interesting one. I'll discuss the thinking behind the exercise, followed by my solution.</p><p>Here's the exercise itself:
</p><p>Write a recursive function that calculates the value of Fibonacci numbers. These are your acceptance criteria:
</p><ul><li>Calculating fib(250) <strong>must</strong> return 7896325826131730509282738943634332893686268675876375</li>
<li>The function <strong>must</strong> use recursion. No intermediary data structures, etc.</li>
<li>The implementation <strong>must</strong> be written in pure python – no C extension modules, that's cheating.</li>
<li>The function <strong>must</strong> calculate the 250th Fibonacci number in under one second.
You will get extra points if:
</li>
<li>You can also demonstrate a proof for the Reciprocal Fibonacci constant, meeting the following conditions
<ul>
<li>Your proof <strong>must</strong> also run in under one second</li>
<li>Your proof <strong>must not</strong> duplicate any of the concerns addressed by your original Fibonacci function implementation.</li>
<li>Your proof <strong>is</strong> allowed to call into the Fibonacci function though!</li></ul></li></ul>The reciprocal Fibonacci constant is defined <a href="http://mathworld.wolfram.com/ReciprocalFibonacciConstant.html">here</a>.
Here's a couple of hints about things to look for:
<ul><li>Memoization</li>
<li>Function decorators</li></ul><p></p><h4>Well now. What's the point of this exercise?</h4><p>The point here is to get people thinking about function composition. Re-use is so often quoted as being important in our work, and yet in our industry at large, we often see developers failing to apply even basic functional (de!)composition, let alone OOP and other high level programming paradigms.</p><p></p>Properly composed functions are a basic unit of abstraction in most languages, and <a href="http://www.python.org/">Python</a> is no exception.<p></p><h4>Implementation</h4><p>The performance requirements of the spec are really a red herring. The key to this exercise is to avoid duplicating the performance enhancing code, which is required to avoid the slowdown (and possible program failure) resulting from excessive recursion. I decided to abstract a simple caching algorithm as a function decorator, which is then applied to both recursive functions (fibonacci and the fib' constant implementation). You could use the 'memoized' decorator anywhere you have a function that is <a href="http://en.wikipedia.org/wiki/Referential_transparency_%28computer_science%29">referentially transparent</a>. Here's the code:
</p><pre class="sh_python sh_sourceCode"><span class="sh_comment">#!/usr/bin/env python</span>
<span class="sh_keyword">def</span> <span class="sh_function">memoized</span><span class="sh_symbol">(</span><span class="sh_variable">fun</span><span class="sh_symbol">):</span>
<span class="sh_variable">target</span> <span class="sh_symbol">=</span> <span class="sh_variable">fun</span>
<span class="sh_variable">cache</span> <span class="sh_symbol">=</span> <span class="sh_symbol">{}</span>
<span class="sh_keyword">def</span> <span class="sh_function">memoized</span><span class="sh_symbol">(*</span><span class="sh_variable">args</span><span class="sh_symbol">,</span> <span class="sh_symbol">**</span><span class="sh_variable">kwargs</span><span class="sh_symbol">):</span>
<span class="sh_keyword">if</span> <span class="sh_variable">cache</span><span class="sh_symbol">.</span><span class="sh_function">has_key</span><span class="sh_symbol">(</span><span class="sh_variable">args</span><span class="sh_symbol">[</span><span class="sh_number">0</span><span class="sh_symbol">]):</span>
<span class="sh_keyword">return</span> <span class="sh_variable">cache</span><span class="sh_symbol">[</span><span class="sh_variable">args</span><span class="sh_symbol">[</span><span class="sh_number">0</span><span class="sh_symbol">]]</span>
<span class="sh_variable">result</span> <span class="sh_symbol">=</span> <span class="sh_function">target</span><span class="sh_symbol">(*</span><span class="sh_variable">args</span><span class="sh_symbol">,</span> <span class="sh_symbol">**</span><span class="sh_variable">kwargs</span><span class="sh_symbol">)</span>
<span class="sh_variable">cache</span><span class="sh_symbol">[</span><span class="sh_variable">args</span><span class="sh_symbol">[</span><span class="sh_number">0</span><span class="sh_symbol">]]</span> <span class="sh_symbol">=</span> <span class="sh_variable">result</span>
<span class="sh_keyword">return</span> <span class="sh_variable">result</span>
<span class="sh_keyword">return</span> <span class="sh_variable">memoized</span>
@<span class="sh_variable">memoized</span>
<span class="sh_keyword">def</span> <span class="sh_function">fib</span><span class="sh_symbol">(</span><span class="sh_variable">n</span><span class="sh_symbol">):</span>
<span class="sh_keyword">if</span> <span class="sh_variable">n</span> <span class="sh_symbol"><</span> <span class="sh_number">2</span><span class="sh_symbol">:</span> <span class="sh_keyword">return</span> <span class="sh_variable">n</span>
<span class="sh_keyword">return</span> <span class="sh_function">fib</span><span class="sh_symbol">(</span><span class="sh_variable">n</span> <span class="sh_symbol">-</span> <span class="sh_number">1</span><span class="sh_symbol">)</span> <span class="sh_symbol">+</span> <span class="sh_function">fib</span><span class="sh_symbol">(</span><span class="sh_variable">n</span> <span class="sh_symbol">-</span> <span class="sh_number">2</span><span class="sh_symbol">)</span>
@<span class="sh_variable">memoized</span>
<span class="sh_keyword">def</span> <span class="sh_function">fibConstant</span><span class="sh_symbol">(</span><span class="sh_variable">n</span><span class="sh_symbol">):</span>
<span class="sh_keyword">if</span><span class="sh_symbol">(</span><span class="sh_variable">n</span> <span class="sh_symbol">==</span> <span class="sh_number">1</span><span class="sh_symbol">):</span>
<span class="sh_keyword">return</span> <span class="sh_symbol">(</span><span class="sh_number">1.0</span> <span class="sh_symbol">/</span> <span class="sh_function">fib</span><span class="sh_symbol">(</span><span class="sh_variable">n</span><span class="sh_symbol">))</span>
<span class="sh_keyword">else</span><span class="sh_symbol">:</span>
<span class="sh_keyword">return</span> <span class="sh_symbol">(</span><span class="sh_number">1.0</span> <span class="sh_symbol">/</span> <span class="sh_function">fib</span><span class="sh_symbol">(</span><span class="sh_variable">n</span><span class="sh_symbol">))</span> <span class="sh_symbol">+</span> <span class="sh_function">fibConstant</span><span class="sh_symbol">(</span><span class="sh_variable">n</span> <span class="sh_symbol">-</span> <span class="sh_number">1.0</span><span class="sh_symbol">)</span>
<span class="sh_keyword">if</span> <span class="sh_variable">__name__</span><span class="sh_symbol">==</span><span class="sh_string">'__main__'</span><span class="sh_symbol">:</span>
<span class="sh_preproc">import</span> <span class="sh_variable">profile</span>
<span class="sh_variable">profile</span><span class="sh_symbol">.</span><span class="sh_function">run</span><span class="sh_symbol">(</span><span class="sh_string">'print fib(250)'</span><span class="sh_symbol">)</span>
<span class="sh_variable">profile</span><span class="sh_symbol">.</span><span class="sh_function">run</span><span class="sh_symbol">(</span><span class="sh_string">'print fibConstant(93.0)'</span><span class="sh_symbol">)</span>
</pre><p></p>Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-4296268859164767587.post-71818837856726009672008-02-11T23:59:00.010+00:002008-04-07T15:34:21.627+00:00I recently noticed <a href="http://beust.com/weblog/archives/000461.html">this</a> post and wanted to make some general comments about Erlang,
functional programming and OO. I've decided to address some of these issues over the coming weeks, as much of this debate is raging in my
workplace currently. But for now, I'd like to address a question raised some weeks ago by one of my co-workers. Along with some fellow Erlangers,
I was demonstrating Erlang and OTP to selected members of our 60 strong engineering group during a planning meeting in Dublin. One of the
attendees asked a telling question: "what about when things change." Reading between the lines, I could tell that my friend and colleague was really
asking about the ease with which you can modify (and thereby maintain and potentially extend) Erlang code. His point of comparison was Java or,
to be less specific, languages/platforms that support the Object Oriented paradigm.
<p>
The ways in which an object oriented language/platform facilitates
change are many, but perhaps the best known of these is polymorphism. The ability to change behaviour at runtime by supplying an invocation target
which shares a common, base interface, but overrides the target function with its own implementation: this "ability" is not unique to OO platforms
at all. The mechanism used by languages such as C++, Java and .NET varies a little, but the terminology does not. In a langauge that uses manifest
typing, such virtual resolution of an invocation target, relies on a mechnism known as <i>dynamic binding</i> (sometimes also called <i>dynamic
dispatch</i>). Functional languages are not without support for changing their invocation targets at runtime, but in general, use a different
set of mechanics to do so. The strongly typed, non-strict, functional programming langauge <a href="http://www.haskell.org/">Haskell</a>, for
example, has full support for polymorphic types and functions, but the mechanics of polymorphism are wildly different to those used by imperative
languages.
</p>
<p>
Another set of langauges also support polymorphism, without the use of parameterized types and type constraints found in
<a href="http://www.haskell.org/">Haskell</a> and minus the implementation and/or interface inheritance requirements of C++, Java and the .NET
family of languages. These <i>dynamically typed</i> languages include <a href="http://www.python.org/">Python</a>,
<a href="http://www.ruby-lang.org/">Ruby</a>, and <a href="http://www.perl.org/">Perl</a>, to name but a few. Such langauges support the kind of
ad-hoc polymorphism found in other imperative, Object Oriented langauges. It is both to this family of langauges, as well as the family of <i>
functional programming</i> langauges, that Erlang belongs.
</p>
<p>
Erlang does support polymorphism, as do most other functional languages. Erlang is not strongly typed, it is dynamically typed;
In this respect, Erlang is unlike the Algol familiy of langauges to which Java and C# belong. This difference is not as apparent as Erlang's
functional heritage, but can be just as conceptually tricky to understand if you have little or no background in dynamic languages.
The polymorphism offerred by dynamically typed langauges isn't based on the 'class' of a reference, but rather on the messages being passed;
A concept sometimes called <i>duck typing</i>. Because Erlang's type system is not rooted in the mechanics of classification and implementation
inheritance, it is sometimes difficult to see that <i>duck typing</i> is there, just beneath the surface. I suspect this is why some folks who're
apparently from a <i>dynamic-langauges</i> background fail to see it.
<br>
The idea behind duck typing is that you can send a message to an object regardless of (the object's) type, as long as the callee can respond
to this message. Here's a mind blowingly simple example:
</p><pre class="sh_python sh_sourceCode"><span class="sh_comment">#!/usr/bin/env python</span>
<span class="sh_comment"># demo.py</span>
<span class="sh_keyword">class</span> <span class="sh_variable">Foo</span><span class="sh_symbol">:</span>
<span class="sh_keyword">def</span> <span class="sh_function">quack</span><span class="sh_symbol">():</span> <span class="sh_keyword">print</span> <span class="sh_string">'Foo quacks like a duck'</span>
<span class="sh_keyword">class</span> <span class="sh_variable">Bar</span><span class="sh_symbol">:</span>
<span class="sh_keyword">def</span> <span class="sh_function">quack</span><span class="sh_symbol">():</span> <span class="sh_keyword">print</span> <span class="sh_string">'Bar quacks like a duck'</span>
<span class="sh_keyword">def</span> <span class="sh_function">test</span><span class="sh_symbol">(</span><span class="sh_variable">duck</span><span class="sh_symbol">):</span>
<span class="sh_variable">duck</span><span class="sh_symbol">.</span><span class="sh_function">quack</span><span class="sh_symbol">()</span>
<span class="sh_variable">foo</span> <span class="sh_symbol">=</span> <span class="sh_function">Foo</span><span class="sh_symbol">()</span>
<span class="sh_variable">bar</span> <span class="sh_symbol">=</span> <span class="sh_function">Bar</span><span class="sh_symbol">()</span>
<span class="sh_symbol">[</span> <span class="sh_function">test</span><span class="sh_symbol">(</span><span class="sh_variable">duck</span><span class="sh_symbol">)</span> <span class="sh_keyword">for</span> <span class="sh_variable">duck</span> <span class="sh_keyword">in</span> <span class="sh_symbol">[</span> <span class="sh_variable">foo</span><span class="sh_symbol">,</span> <span class="sh_variable">bar</span> <span class="sh_symbol">]</span> <span class="sh_symbol">]</span>
</pre>
You'll note that Foo and Bar aren't part of the same inheritance hierarchy! This doesn't matter here, as we only care that the
message being sent is supported by the callee. Now to Erlang, which supports polymorphism like this in two ways. The first is simple, and involves
calling into modules:
<pre class="sh_prolog sh_sourceCode"><span class="sh_comment">%%% here's our consumer</span>
<span class="sh_function">consumer</span><span class="sh_symbol">(</span><span class="sh_variable">Mod</span><span class="sh_symbol">)</span> <span class="sh_symbol">-></span> <span class="sh_variable">Mod</span><span class="sh_symbol">:</span><span class="sh_function">execute</span><span class="sh_symbol">().</span>
</pre>
This consumer's use of Mod is polymorphic, given that you can pass any module (i.e. an atom which is a module name) which supports a function
with signature execute/0. There's no type constraint here that stops us from passing in any old nonsense (like a list or number) instead of a
module name. You can use a combination of pattern matching and guards to enforce some degree of type safety if you need it (which
you will sometimes and wont others). The only constraint we can really apply here is that Mod is an atom, unless we create a guard function that
tries to evaluate the atom and see if we can treat it like a module with a given function. For example:
<pre class="sh_prolog sh_sourceCode"><span class="sh_comment">%%% We want to ensure that ....</span>
<span class="sh_symbol">-</span><span class="sh_function">module</span><span class="sh_symbol">(</span>demo<span class="sh_symbol">).</span>
<span class="sh_symbol">-</span><span class="sh_function">compile</span><span class="sh_symbol">(</span>export<span class="sh_variable">_</span>all<span class="sh_symbol">).</span>
<span class="sh_function">is_compatible_module</span><span class="sh_symbol">(</span><span class="sh_variable">CallHandlerMod</span><span class="sh_symbol">)</span> <span class="sh_symbol">-></span> lists<span class="sh_symbol">:</span><span class="sh_function">any</span><span class="sh_symbol">(</span>
<span class="sh_function">fun</span><span class="sh_symbol">(</span><span class="sh_cbracket">{</span>call<span class="sh_symbol">,</span><span class="sh_number">4</span><span class="sh_cbracket">}</span><span class="sh_symbol">)</span> <span class="sh_symbol">-></span> true<span class="sh_symbol">;</span>
<span class="sh_symbol">(</span><span class="sh_variable">_</span><span class="sh_symbol">)</span> <span class="sh_symbol">-></span> false
end<span class="sh_symbol">,</span>
<span class="sh_variable">CallHandlerMod</span><span class="sh_symbol">:</span><span class="sh_function">module_info</span><span class="sh_symbol">(</span>functions<span class="sh_symbol">)</span>
<span class="sh_symbol">).</span>
<span class="sh_function">make_call</span><span class="sh_symbol">(</span><span class="sh_variable">CallHandlerMod</span><span class="sh_symbol">,</span> <span class="sh_variable">CallData</span><span class="sh_symbol">)</span>
when <span class="sh_function">is_atom</span><span class="sh_symbol">(</span><span class="sh_variable">CallHandlerMod</span><span class="sh_symbol">)</span> <span class="sh_symbol">-></span>
<span class="sh_function">make_call</span><span class="sh_symbol">(</span><span class="sh_function">is_compatible_module</span><span class="sh_symbol">(</span><span class="sh_variable">CallHandlerMod</span><span class="sh_symbol">),</span> <span class="sh_variable">CallHandlerMod</span><span class="sh_symbol">,</span> <span class="sh_variable">CallData</span><span class="sh_symbol">).</span>
<span class="sh_function">make_call</span><span class="sh_symbol">(</span>true<span class="sh_symbol">,</span> <span class="sh_variable">CallHandlerMod</span><span class="sh_symbol">,</span> <span class="sh_variable">CallData</span><span class="sh_symbol">)</span> <span class="sh_symbol">-></span>
<span class="sh_comment">%% do some work, and then</span>
<span class="sh_variable">CallHandlerMod</span><span class="sh_symbol">:</span><span class="sh_function">call</span><span class="sh_symbol">(</span><span class="sh_number">13</span><span class="sh_symbol">,</span> <span class="sh_variable">CallData</span><span class="sh_symbol">,</span> <span class="sh_string">"ignored"</span><span class="sh_symbol">,</span> <span class="sh_string">"deleted"</span><span class="sh_symbol">);</span>
<span class="sh_function">make_call</span><span class="sh_symbol">(</span>false<span class="sh_symbol">,</span> <span class="sh_variable">_</span><span class="sh_symbol">,</span> <span class="sh_variable">_</span><span class="sh_symbol">)</span> <span class="sh_symbol">-></span> false<span class="sh_symbol">.</span>
</pre>
<p>
Now let's consider receiving messages from other processes. When you send a message (asynchronously) to another process, you pass the message to
a process identifier (Pid) using the send operator, like so:
</p>
<pre class="sh_prolog sh_sourceCode"><span class="sh_variable">SomePid</span> <span class="sh_symbol">!</span> <span class="sh_cbracket">{</span> message<span class="sh_symbol">,</span> <span class="sh_string">"a message..."</span> <span class="sh_cbracket">}</span>
</pre>
<p>
You can also call a registered process simply by using the atom it was registered with. Unless we're working with a registered process, we'll
typically be passed the Pid either as a function argument, like so:
</p>
<pre class="sh_prolog sh_sourceCode"><span class="sh_function">someFunction</span><span class="sh_symbol">(</span><span class="sh_cbracket">{</span><span class="sh_variable">Name</span><span class="sh_symbol">,</span> <span class="sh_variable">SenderPid</span><span class="sh_cbracket">}</span><span class="sh_symbol">)</span>
<span class="sh_symbol">-></span>
<span class="sh_comment">%%% ... some more code....</span>
<span class="sh_variable">SenderPid</span> <span class="sh_symbol">!</span> <span class="sh_cbracket">{</span>message<span class="sh_symbol">,</span> <span class="sh_variable">Data</span><span class="sh_cbracket">}</span><span class="sh_symbol">.</span>
</pre>
or in an incoming message, where the sender passed its own Pid (self) like so:
<pre class="sh_prolog sh_sourceCode"><span class="sh_variable">TargetPid</span> <span class="sh_symbol">!</span> <span class="sh_cbracket">{</span><span class="sh_function">self</span><span class="sh_symbol">(),</span> <span class="sh_cbracket">{</span>message<span class="sh_symbol">,</span> <span class="sh_variable">Data</span><span class="sh_cbracket">}}</span></pre>
which is reused by the receiver later on:
<pre class="sh_prolog sh_sourceCode"><span class="sh_function">someFunction</span><span class="sh_symbol">()</span> <span class="sh_symbol">-></span>
receive
<span class="sh_cbracket">{</span><span class="sh_variable">SenderPid</span><span class="sh_symbol">,</span> <span class="sh_cbracket">{</span>message<span class="sh_symbol">,</span> <span class="sh_variable">Data</span><span class="sh_cbracket">}}</span>
<span class="sh_symbol">-></span> <span class="sh_comment">%%% some code...</span>
<span class="sh_variable">SenderPid</span> <span class="sh_symbol">!</span> <span class="sh_cbracket">{</span>response<span class="sh_symbol">,</span> <span class="sh_variable">Response</span><span class="sh_cbracket">}</span>
end<span class="sh_symbol">.</span>
</pre>
Note that there is no 'hard' contract between sender and reciever, except for the structure of the data being passed between the two. This isn't
really much diferrent from the inter-module calls we made earlier. Here, instead of a module name passed in as the "Mod" argument, we have a process
identifier with which we interact. Instead of a function signature consisting of modulename:function-name/arity, we have a signature made up entirely
of the kind of data structure the callee will accept.
If the caller passes, for example, the tuple {foo, bar}, then several things can happen:
<ul>
<li>If the callee understands this message, the call effectively succeeds. Just as with a polymorphic method call, we don't know what the
implementing class will actually do!</li>
<li>If the callee ignores this message (there are several mechanisms for doing so), then the call is ignored. Note that this option (silent failure)
is dependant on the server (e.g. the callee), not the client.</li>
<li>If the callee decides to explode on receiving unrecognisable mail, the usual trap_exit Erlang error handling dance ensues.</li>
</ul>
<p>
Another option, if the sender supplied their own Pid, is for the server (the callee) to respond with an {i_dont_understand_you} message,
leaving the client to decide what's appropriate. This mechanism also allows for changing recievers dynamically. You can pass any process identifier
you like (even changing the server Pid at runtime) and the client code doesn't need to change to accomodate this. This decoupling is far looser than
a direct method call, even when it is dispatched polymorphically.
Erlang systems are typically highly decoupled and this is not only due to the nature of asynchronous message passing, but also because of Erlang's
dynamically typed nature.
</p><p>In a subsequent post, we'll look at how inter (erlang) process messaging can be used to implement continuations.</p>
<p></p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4296268859164767587.post-12377525542299145622007-05-10T10:37:00.000+00:002007-05-10T10:39:07.394+00:00<span style="font-family:Verdana;font-size:85%;">I've recently blogged over at the <a href="http://web21c.bt.com">bt sdk portal</a> on installing dependencies for the python sdk. Check it out <a href="http://sdk.bt.com/Community/Blogs/tabid/71/EntryID/46/Default.aspx">here</a>.</span>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4296268859164767587.post-19648832002214192432007-05-08T11:16:00.001+00:002008-06-19T14:13:52.844+00:00<div style="text-align: left;"><span style="font-family: verdana;"><span style="font-size:85%;">Want to make phones ring or send SMS messages in 1 line of code? Want to do this in python, php, java, or even .NET!? Get yourself over to <a href="http://web21c.bt.com">http://web21c.bt.com</a> and download BT's new web21c sdk then.</span></span></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4296268859164767587.post-79590234027306729592007-04-16T13:07:00.002+00:002008-06-19T14:13:17.761+00:00<span style="font-family:verdana;font-size:85%;">One of the things I like about JUnit 4 is to see anotations replace the naming convention required by earlier versions. I like the declarative approach so much, in fact, that I've extended pyunit (the standard python unit testing framework) with some simple hacks that yield the same syntax. </span>
<span style="font-family:Verdana;font-size:85%;"></span>
<span style="font-family:Verdana;font-size:85%;">By including the pyunit2.py module, you can now write your test methods like this:</span>
<span style="font-family:Verdana;font-size:85%;"></span>
<span style="font-family:courier new;font-size:85%;">@test</span>
<span style="font-family:Courier New;font-size:85%;">@expected_exception( NameError )</span>
<span style="font-family:Courier New;font-size:85%;">def a_simple_example_test( self ): #...etc</span>
<span style="font-family:Courier New;font-size:85%;"></span>
<span style="font-family:verdana;font-size:85%;">The module's gone up on aspn for posterity. You'll find it <a href="http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/abcdefg">here</a>.</span>
<span style="font-family:Verdana;font-size:85%;"></span>511475Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4296268859164767587.post-55321970320894313082007-04-13T13:06:00.000+00:002007-04-13T13:26:38.521+00:00<span style="font-family:verdana;font-size:85%;">I've been planning on updating this blog for some time now, but have been busy with a new job (more on that in a future post). Over the couple of days, I've been looking at some of the various services that are appearing on the web. In the course of playing around with the Google and Yahoo local search APIs, I've noticed that they don't seem to do much outside of the US (not in the UK, at least).
This is most irksome, as I was planning on using their features in a couple of sample mashups. Hopefully these services will start to benefit those of us on the other side of the atlantic some time soon!</span>Unknownnoreply@blogger.com0