<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-6753084968628985846</id><updated>2011-04-21T16:09:20.025-07:00</updated><category term='ruby'/><category term='ws'/><category term='webservices'/><category term='macosx'/><category term='decorator'/><category term='singleton'/><category term='interoperability'/><category term='codemash'/><category term='sucksrocks'/><category term='cargo cult'/><category term='bitbacker'/><category term='hfs+'/><category term='pycon'/><category term='vb'/><category term='C#'/><category term='rest'/><category term='global'/><category term='pycon2007'/><category term='python'/><category term='web.py'/><category term='languages'/><category term='search'/><category term='simplejson'/><category term='undeletable'/><category term='cjson'/><category term='nose'/><category term='slashdot'/><category term='testing'/><category term='blogging'/><category term='json'/><title type='text'>Gary Bernhardt's OLD Blog</title><subtitle type='html'>This is my old blog. You want my &lt;a href="http://blog.extracheese.org"&gt;new&lt;/a&gt; one.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>19</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-1933526456797271088</id><published>2007-12-22T13:03:00.000-08:00</published><updated>2008-09-06T18:22:11.558-07:00</updated><title type='text'>New blog URL</title><content type='html'>If you're seeing this, you're looking at my old blog.  My new blog is at &lt;a href="http://blog.extracheese.org"&gt;blog.extracheese.org&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-1933526456797271088?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/1933526456797271088/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=1933526456797271088' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/1933526456797271088'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/1933526456797271088'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/12/new-blog-url.html' title='New blog URL'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-908827919673100974</id><published>2007-12-19T16:02:00.000-08:00</published><updated>2007-12-19T16:05:32.436-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='blogging'/><title type='text'>Blog woes</title><content type='html'>&lt;p&gt;Last night, I switched my blog to tumblr; this morning, I switched it back to Blogger.  Over the last two days, I've spent a ridiculous amount of time and effort trying to make the switch without breaking any links.  I'll spare the details, but it involved a lot of mod_rewrite, a PHP script written by &lt;a href="http://henrik.nyh.se/2007/06/tumblr-in-a-subdirectory"&gt;Henrik Nyh&lt;/a&gt; that proxies all requests to tumblr, and a huge list of URLs mapping the old Blogger ones to the new tumblr ones.&lt;/p&gt;

&lt;p&gt;I found the proxy-with-a-PHP-script thing distasteful, but the lack of decent tag support was the thing that ultimately made me give up.  I have a Python-specific feed that gets aggregated by &lt;a href="http://www.planetpython.org"&gt;the unofficial planet python&lt;/a&gt;, so whatever I switch to needs to be able to generate tag-specific feeds.  Tumblr does let you add tags to your posts, but it doesn't seem to actually do anything with them.&lt;/p&gt;

&lt;p&gt;So, I'm in search of blogging software.  I want something that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Is written in Python or Ruby.  Python because I know it very well; Ruby because I want to know it better.&lt;/li&gt;
  &lt;li&gt;Is simple.  I'm not interested in anything built in Django or TurboGears or Rails.  Preferably, it would be something that runs on my local machine, generating the static files that make up the live blog.&lt;/li&gt;
  &lt;li&gt;Allows custom URLs but has sane defaults.  (I.e., no exposed serial integer keys.)&lt;/li&gt;
  &lt;li&gt;Supports tags and can generate subfeeds based on them, as well as human-readable post lists that are filtered by them.  (For example, with my current blog you can look at &lt;a href="http://blog.extracheese.org/search/label/python"&gt;only Python posts&lt;/a&gt; if you want to.)&lt;/li&gt;
  &lt;li&gt;Does not involve a database.  (Not even SQLite.)&lt;/li&gt;
  &lt;li&gt;Reads the posts out of plain HTML files, which I will write by hand.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've looked around for something like this, but everything seems to be either big (e.g., Pyblosxom and Typo) or someone's weekend project that never got touched again.  I'm sure that the bigger ones are quite good at what they do, but I just want something that takes my plaintext files and generates an appropriate URL structure.  It can do it offline or online - I don't care - but it's got to be simple and require no complicated installation or configuration.&lt;/p&gt;

&lt;p&gt;So, any ideas, or am I starting a new project? :)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-908827919673100974?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/908827919673100974/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=908827919673100974' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/908827919673100974'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/908827919673100974'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/12/blog-woes.html' title='Blog woes'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-2711388190710700706</id><published>2007-10-09T15:03:00.001-07:00</published><updated>2007-10-09T15:04:16.877-07:00</updated><title type='text'>Tubes by proxy</title><content type='html'>&lt;p&gt;On Friday, I am bored and want video games.  But no Internet service!  How to download new games to Wii?!&lt;/p&gt;

&lt;p&gt;Assets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One &lt;a href="http://en.wikipedia.org/wiki/Vx8300"&gt;VX8300&lt;/a&gt; cellular telephone.&lt;/li&gt;
&lt;li&gt;One Macintosh Book Professional.&lt;/li&gt;
&lt;li&gt;One Nintendo Wii.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Solution:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Connect VX8300 to MacBook via Bluetooth.&lt;/li&gt;
&lt;li&gt;Configure MacBook to use VX8300 &lt;a href="http://www.appleology.com/2006/09/27/setup-bluetooth-dun-with-your-verizon-mobile-and-your-mac/"&gt;as a modem&lt;/a&gt; for free.  This is an undocumented Verizon feature!&lt;/li&gt;
&lt;li&gt;Put MacBook in ad-hoc wireless mode.&lt;/li&gt;
&lt;li&gt;Enable internet sharing on MacBook.&lt;/li&gt;
&lt;li&gt;Inform Wii of wireless network.&lt;/li&gt;
&lt;li&gt;Access Wii Store!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Full data path: Internet -&gt; Verizon Wireless -&gt; CDMA 2000 (1x) -&gt; VX8300 -&gt; Bluetooth -&gt; MacBook -&gt; 802.11g -&gt; Wii.&lt;/p&gt;

&lt;p&gt;Operation was a success.  &lt;a href="http://www.gamefaqs.com/console/snes/game/563530.html"&gt;Breath of Fire II&lt;/a&gt; acquired.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-2711388190710700706?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/2711388190710700706/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=2711388190710700706' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/2711388190710700706'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/2711388190710700706'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/10/tubes-by-proxy.html' title='Tubes by proxy'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-6233540273403132039</id><published>2007-09-13T13:48:00.001-07:00</published><updated>2007-09-13T13:48:45.441-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='undeletable'/><category scheme='http://www.blogger.com/atom/ns#' term='hfs+'/><category scheme='http://www.blogger.com/atom/ns#' term='bitbacker'/><category scheme='http://www.blogger.com/atom/ns#' term='macosx'/><title type='text'>Undeletable zombie files on OS X</title><content type='html'>&lt;p&gt;Somehow, one of &lt;a href="http://www.bitbacker.com"&gt;Bitbacker&lt;/a&gt;'s system tests occasionally creates an undeletable file.  It happens within a test that generates random filenames, then tries to back up and restore them.  Unfortunately, there are apparently some character sequences that make HFS+ choke.  I've only had this happen twice ever - once on my 17" MacBook Pro, and just now on my brand new 15" MacBook Pro.  Here's what the file looks like:&lt;/p&gt;

&lt;a href="http://www.flickr.com/photos/7135305@N02/1374499748/" title="Photo Sharing"&gt;&lt;img src="http://farm2.static.flickr.com/1275/1374499748_b5f55a07d6_o.png" width="364" height="310" alt="A broken file" /&gt;&lt;/a&gt;

&lt;p&gt;It can't be deleted from Finder:&lt;/p&gt;

&lt;a href="http://www.flickr.com/photos/7135305@N02/1374499728/" title="Photo Sharing"&gt;&lt;img src="http://farm2.static.flickr.com/1399/1374499728_73f476e9d5_o.png" width="399" height="108" alt="Trying to delete a broken file from Finder" /&gt;&lt;/a&gt;

&lt;p&gt;It can't be deleted from the console:&lt;/p&gt;

&lt;a href="http://www.flickr.com/photos/7135305@N02/1374499742/" title="Photo Sharing"&gt;&lt;img src="http://farm2.static.flickr.com/1397/1374499742_05f2be7c24_o.png" width="341" height="112" alt="Trying to delete a broken file from the console" /&gt;&lt;/a&gt;

&lt;p&gt;And it can't even be "ls"ed:&lt;/p&gt;

&lt;a href="http://www.flickr.com/photos/7135305@N02/1374514026/" title="Photo Sharing"&gt;&lt;img src="http://farm2.static.flickr.com/1321/1374514026_0f614c527a_o.png" width="341" height="112" alt="Trying to ls a broken file" /&gt;&lt;/a&gt;

&lt;p&gt;If I manually move the containing folder to the trash and try to empty it, I get this, which is even more ridiculous than the rest:&lt;/p&gt;

&lt;a href="http://www.flickr.com/photos/7135305@N02/1374529726/" title="Photo Sharing"&gt;&lt;img src="http://farm2.static.flickr.com/1125/1374529726_03b35b822f_o.png" width="399" height="108" alt="Trying to delete a broken file from the trash" /&gt;&lt;/a&gt;

&lt;p&gt;I've googled for undeletable files on OS X, as well as for the specific error messages Finder throws, and tried every suggested fix I found.  And, of course, I've tried to delete the file from within Python:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;&amp;gt;&amp;gt;&amp;gt; os.unlink(os.listdir('.')[0])
Traceback (most recent call last):
  File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1, in &amp;lt;module&amp;gt;
  OSError: [Errno 2] No such file or directory:
  '\\xe4\\xad\\xa9\\xe1\\x84\\x84\\xe1\\x85\\xaf\\[...truncated]'&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;Dear Apple: please replace HFS+.  Seriously, it sucks.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-6233540273403132039?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/6233540273403132039/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=6233540273403132039' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/6233540273403132039'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/6233540273403132039'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/09/undeletable-zombie-files-on-os-x.html' title='Undeletable zombie files on OS X'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-3917142275869415089</id><published>2007-09-07T14:10:00.000-07:00</published><updated>2007-09-07T14:13:56.939-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='global'/><category scheme='http://www.blogger.com/atom/ns#' term='decorator'/><category scheme='http://www.blogger.com/atom/ns#' term='cargo cult'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='singleton'/><title type='text'>Globals and cargo culting</title><content type='html'>&lt;p&gt;&lt;a href="http://blog.tplus1.com"&gt;Matt Wilson&lt;/a&gt;
&lt;a
href="http://blog.tplus1.com/index.php/2007/08/24/when-to-use-globals"&gt;wants&lt;/a&gt;
a module's functions to log to one logger, but he can't change their
interface and he doesn't want to use a global variable.  This is the
kind of thing that decorators are very good at, for better or worse.
Here's a decorator that will do the job:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;def with_logger(fn):
    def new_fn(*args, **kwargs):
        logger = get_singleton_logger()
        return fn(logger=logger, *args, **kwargs)
    return new_fn&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;And here's how to use it to define a function that gets a logger
instance without the caller passing it in:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
&gt;&gt;&gt; @with_logger
... def add(x, y, logger):
...     logger.warning('x + y = %i' % (x + y))
...
&gt;&gt;&gt; add(1, 2)
WARNING:foo:x + y = 3
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;This seems to be exactly what Matt was looking for: there are no
globals, a logger gets injected, and the function's interface hasn't
changed.  But is it a good idea?&lt;/p&gt;

&lt;p&gt;No, it's a ridiculous idea!  It's just a reimplementation of global
variables!  All I've done is come up with a complicated scheme for
injecting a single logger instance into every function in the module.
But that's exactly what a global does!  This is something that
programmers have done over and over again in the name of OO.
Everyone &lt;i&gt;wants&lt;/i&gt; globals, but they go to great lengths to hide
it.&lt;/p&gt;

&lt;p&gt;Here are three possible ways to solve the original logger
problem:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Use a singleton, and have each function retrieve the instance
  that way.&lt;/li&gt;
  &lt;li&gt;Use a decorator that injects the instance into the function's
  argument list each time.  In with_logger, I combined this with a
  singleton.&lt;/li&gt;
  &lt;li&gt;Just use a global.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you choose (1) or (2), the joke's on you.  You're still using a
global, but now you
have &lt;a href="http://regex.info/blog/2006-09-15/247"&gt;two problems&lt;/a&gt;:
global state &lt;i&gt;and&lt;/i&gt; a complicated method for managing it.  There's
no need for that, because we already have a simple method for
injecting instances into a module's functions: globals!&lt;/p&gt;

&lt;p&gt;Of course, sometimes singletons or decorators like with_logging
make sense, but only when they actually &lt;i&gt;do something&lt;/i&gt;.  If all
they do is allow multiple functions to access a single long-living
instance, they're dangerous and needlessly complex.  In almost all
cases, singleton and related techniques are nothing more than
&lt;a href="http://en.wikipedia.org/wiki/Cargo_cult_programming"&gt;cargo
cult programming&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-3917142275869415089?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/3917142275869415089/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=3917142275869415089' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/3917142275869415089'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/3917142275869415089'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/09/globals-and-cargo-culting.html' title='Globals and cargo culting'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-8202635133587447250</id><published>2007-07-23T09:20:00.000-07:00</published><updated>2007-07-23T20:36:05.777-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='interoperability'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='simplejson'/><category scheme='http://www.blogger.com/atom/ns#' term='json'/><category scheme='http://www.blogger.com/atom/ns#' term='cjson'/><title type='text'>When JSON isn't JSON</title><content type='html'>&lt;p&gt;JSON is so simple that you can specify it on an index card, but we &lt;i&gt;still&lt;/i&gt; can't get it right.  For example, here's what happens when &lt;a href="http://undefined.org/python/"&gt;simplejson&lt;/a&gt; and &lt;a href="http://cheeseshop.python.org/pypi/python-cjson"&gt;python-cjson&lt;/a&gt; talk about slashes:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
# simplejson correctly decodes cjson's data
&gt;&gt;&gt; print simplejson.loads(cjson.encode('/'))
/

# cjson fails to decode simplejson's data
&gt;&gt;&gt; print cjson.decode(simplejson.dumps('/'))
\/
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;In this case, the problem is that cjson doesn't handle backslashes correctly.  There are two ways to say "/" in JSON: "/" and "\/".  When encoding, simplejson always escapes slashes, but cjson never does:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
&gt;&gt;&gt; print simplejson.dumps('/')
"\/"
&gt;&gt;&gt; print cjson.encode('/')
"/"
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;The reverse is also true: simplejson knows how to decode "\/", but cjson decodes it incorrectly:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
&gt;&gt;&gt; print simplejson.loads('"\/"')
/
&gt;&gt;&gt; print cjson.decode('"\/"')
\/
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;So there you go: simplejson and cjson don't interoperate.  This bit me when I tried to move BitBacker from simplejson to cjson for performance reasons.  The live alpha server had a few thousand records encoded with simplejson, all of which included slashes.  When I switched to cjson, everything broke because every "/foo/bar" entry in the database came back as "\/foo\/bar".&lt;/p&gt;

&lt;p&gt;As far as I'm concerned, this problem with JSON is actually an argument &lt;i&gt;for&lt;/i&gt; simple data formats like JSON.  If we can't get full interoperability between something as stupidly simple as JSON, how did anyone ever expect &lt;a href="http://www.innoq.com/resources/ws-standards-poster"&gt;WS-*&lt;/a&gt; to work?&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-8202635133587447250?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/8202635133587447250/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=8202635133587447250' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/8202635133587447250'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/8202635133587447250'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/07/when-json-isnt-json.html' title='When JSON isn&apos;t JSON'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-4311117903438109780</id><published>2007-05-27T20:57:00.001-07:00</published><updated>2007-05-27T20:57:42.995-07:00</updated><title type='text'>Adobe, master of the painful install</title><content type='html'>&lt;p&gt;After using Macs for about two years, I've become accustomed to the 
painless installation process there: open the dmg, drag the app to the
Applications folder, and you're done.  Unfortunately, I recently had to
install Acrobat Reader to electronically sign some insurance documents.  They
have managed to come up with a downright pathological installation
process:&lt;/p&gt; 
    
&lt;ol&gt;
    &lt;li&gt;Download AdbeRdr80_DLM_en_US_i386.dmg.&lt;/li&gt;
    &lt;li&gt;Open the dmg.&lt;/li&gt;
    &lt;li&gt;DownloadReader.pkg is inside; open it.&lt;/li&gt;
    &lt;li&gt;Click through some stuff.&lt;/li&gt;
    &lt;li&gt;Wait for the "Adobe Reader Download Manager" to download
    &lt;i&gt;another&lt;/i&gt; 23 MB dmg.&lt;/li&gt;
    &lt;li&gt;The "Download Manager" has silently quit (what?), but the dmg is now
    mounted.  Open "Adobe Reader 8 Installer.app" and wait for it to
    finish.&lt;/li&gt;
    &lt;li&gt;The "Installer" has silently quit (what?), but it must be done, 
    because Acrobat is now running.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is amazing.  When I saw the very first pkg file, I was annoyed: I hate
installers and they're rare in the Mac world.  As I kept installing and
installing, it was almost surreal.  In the above list, there are three times
where something is being installed.  Working back from Acrobat itself,&lt;/p&gt;
    
&lt;ol&gt;
    &lt;li&gt;"Adobe Reader 8 Installer.app" installs "Adobe reader.app", so it's
    the installer.
    &lt;li&gt;The "Download Manager" installs that, so it's the installer installer.
    &lt;li&gt;DownloadReader.pkg installed that, so it's the installer installer
    installer.
&lt;/ol&gt;

&lt;p&gt;So, while most Mac programs have shed the archaic notion of an installer
altogether, Adobe Acrobat Reader has a program that installs a program that
installs a program that installs the software you actually need.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-4311117903438109780?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/4311117903438109780/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=4311117903438109780' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/4311117903438109780'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/4311117903438109780'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/05/adobe-master-of-painful-install.html' title='Adobe, master of the painful install'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-8168537727794664946</id><published>2007-05-22T19:08:00.000-07:00</published><updated>2007-05-22T19:09:11.525-07:00</updated><title type='text'>Texturing and programming</title><content type='html'>&lt;p&gt;Flayra just posted a &lt;a href="http://www.unknownworlds.com/blog/2007/05/natural_selection_2_texturing.html"&gt;video&lt;/a&gt; of one of the Natural Selection 2 artists texturing a 3D model.  I've done a bit of modeling and texturing (poorly, of course), so it's awesome to watch someone who's really good.  It also reminded me of how I work on &lt;a href="http://blog.extracheese.org/2007/02/introducing-another-wildly-ambitious.html"&gt;RESTdb&lt;/a&gt;, though.  I always have my heavily modified &lt;a href="http://jeffwinkler.net/2006/04/27/keeping-your-nose-green/"&gt;nosy&lt;/a&gt; running in its own terminal window, and it gives me constant feedback on the effects of every little change I make.&lt;/p&gt;

&lt;p&gt;In the video, it's obvious that constant feedback is critical to the process.  If the artist tried to create the entire texture without seeing it applied to the mesh, the process would take much longer, yield poorer results, or probably both.  As far as I'm concerned, the same applies to programming: if you use a bit of TDD, along with a continuous test runner like nosy, the programming process becomes more organic and approachable.  It becomes easier to break the problem down into pieces that can be tackled easily, and you have a better sense of the scope of your changes.&lt;/p&gt;

&lt;p&gt;If you haven't tried nosy yet, do it!  It's very easy - just download nosy.py, stick it in your project's directory, and run it.  It's only about 25 lines long, but it can have a huge impact on how you work.  If you're still not convinced, check out &lt;a href="http://jeffwinkler.net/"&gt;Jeff Winkler&lt;/a&gt;'s &lt;a href="http://showmedo.com/videos/video?name=UsingNoseyForPythonTesting_jeffW&amp;fromSeriesID=7"&gt;screencast&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-8168537727794664946?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/8168537727794664946/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=8168537727794664946' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/8168537727794664946'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/8168537727794664946'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/05/texturing-and-programming.html' title='Texturing and programming'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-3669654063493988450</id><published>2007-04-07T13:55:00.001-07:00</published><updated>2007-06-22T15:37:58.016-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='nose'/><title type='text'>Are your tests lying to you?</title><content type='html'>&lt;p&gt; If you've written a test for a module, and the module is changed in the
future, there are three things that can happen:&lt;/p&gt;

&lt;ol&gt;
    &lt;li&gt;The test keeps passing because nothing is broken.  (Good.)&lt;/li&gt;
    &lt;li&gt;The test fails because something is wrong.  (Great - this is the
    test's job!)&lt;/li&gt;
    &lt;li&gt;The test keeps passing, but it silently stops testing the thing it
    claims to (BAD, BAD, BAD!).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Scenario 3 above is very dangerous, and it's a major problem in testing.
What you have in that situation is a lying test: it says "I'm testing feature
x," but actually passes without doing so.  In other words, you have a test
that no longer warns you if you break something.&lt;/p&gt; 

&lt;p&gt;If you've not been bitten by this, it might not be an obvious problem.  To
make it a little more clear, let's look at a toy example (in Python, of
course!)  Here's a silly WebClient class and its test.&lt;/p&gt;

&lt;blockquote&gt;&lt;div class="highlight"&gt;&lt;pre&gt; 1 &lt;span style="color: #AA22FF; font-weight: bold"&gt;class&lt;/span&gt; &lt;span style="color: #0000FF"&gt;WebClient&lt;/span&gt;:
 2     &lt;span style="color: #BB4444; font-style: italic"&gt;&amp;quot;&amp;quot;&amp;quot;An HTTP client that supports both SSL and plain connections&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
 3     &lt;span style="color: #AA22FF; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #00A000"&gt;__init__&lt;/span&gt;(&lt;span style="color: #AA22FF"&gt;self&lt;/span&gt;):
 4         &lt;span style="color: #AA22FF"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;use_ssl &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #AA22FF"&gt;False&lt;/span&gt;
 5 
 6     &lt;span style="color: #AA22FF; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #00A000"&gt;get&lt;/span&gt;(&lt;span style="color: #AA22FF"&gt;self&lt;/span&gt;, url):
 7         &lt;span style="color: #008800; font-style: italic"&gt;# Hand any request off to external functions&lt;/span&gt;
 8         &lt;span style="color: #AA22FF; font-weight: bold"&gt;if&lt;/span&gt; &lt;span style="color: #AA22FF"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;use_ssl:
 9             &lt;span style="color: #AA22FF; font-weight: bold"&gt;return&lt;/span&gt; get_with_ssl(url)
10         &lt;span style="color: #AA22FF; font-weight: bold"&gt;else&lt;/span&gt;:
11             &lt;span style="color: #AA22FF; font-weight: bold"&gt;return&lt;/span&gt; get_without_ssl(url)
12 
13 &lt;span style="color: #AA22FF; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #00A000"&gt;test_web_client&lt;/span&gt;():
14     &lt;span style="color: #008800; font-style: italic"&gt;# Make sure everything works with normal HTTP&lt;/span&gt;
15     client &lt;span style="color: #666666"&gt;=&lt;/span&gt; WebClient()
16     &lt;span style="color: #AA22FF; font-weight: bold"&gt;assert&lt;/span&gt; client&lt;span style="color: #666666"&gt;.&lt;/span&gt;get(&lt;span style="color: #BB4444"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;) &lt;span style="color: #666666"&gt;==&lt;/span&gt; expected_data &lt;span style="color: #008800; font-style: italic"&gt;#defined elsewhere&lt;/span&gt;
17 
18     &lt;span style="color: #008800; font-style: italic"&gt;# Make sure everything works with SSL as well&lt;/span&gt;
19     client&lt;span style="color: #666666"&gt;.&lt;/span&gt;use_ssl &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #AA22FF"&gt;True&lt;/span&gt;
20     &lt;span style="color: #AA22FF; font-weight: bold"&gt;assert&lt;/span&gt; client&lt;span style="color: #666666"&gt;.&lt;/span&gt;get(&lt;span style="color: #BB4444"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;) &lt;span style="color: #666666"&gt;==&lt;/span&gt; expected_data
&lt;/pre&gt;&lt;/div&gt;&lt;/blockquote&gt;

&lt;p&gt;This works fine - the test passes and it tests what it claims to.  But what
happens if someone renames the use_ssl attribute later?&lt;/p&gt;

&lt;blockquote&gt;&lt;div class="highlight"&gt;&lt;pre&gt; 1 &lt;span style="color: #AA22FF; font-weight: bold"&gt;class&lt;/span&gt; &lt;span style="color: #0000FF"&gt;WebClient&lt;/span&gt;:
 2     &lt;span style="color: #AA22FF; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #00A000"&gt;__init__&lt;/span&gt;(&lt;span style="color: #AA22FF"&gt;self&lt;/span&gt;):
 3         &lt;span style="color: #AA22FF"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;us&lt;b style="color: #f00"&gt;ing&lt;/b&gt;_ssl &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #AA22FF"&gt;False&lt;/span&gt;
 4 
 5     &lt;span style="color: #AA22FF; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #00A000"&gt;get&lt;/span&gt;(&lt;span style="color: #AA22FF"&gt;self&lt;/span&gt;, url):
 6         &lt;span style="color: #008800; font-style: italic"&gt;# Hand any request off to external functions&lt;/span&gt;
 7         &lt;span style="color: #AA22FF; font-weight: bold"&gt;if&lt;/span&gt; &lt;span style="color: #AA22FF"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;us&lt;b style="color: #f00"&gt;ing&lt;/b&gt;_ssl:
 8             &lt;span style="color: #AA22FF; font-weight: bold"&gt;return&lt;/span&gt; get_with_ssl(url)
 9         &lt;span style="color: #AA22FF; font-weight: bold"&gt;else&lt;/span&gt;:
10             &lt;span style="color: #AA22FF; font-weight: bold"&gt;return&lt;/span&gt; get_without_ssl(url)
&lt;/pre&gt;&lt;/div&gt;&lt;/blockquote&gt;

&lt;p&gt;Take a look back at the test.  It's no longer testing what it claims to,
because "use_ssl" no longer means anything to WebClient.  The test still
passes, though - it's just that neither of the two get() calls actually uses
SSL.&lt;/p&gt;

&lt;p&gt;This is a serious problem - you need to be able to trust your tests, but
for all you know your tests are giving you false positives.  The question,
then, is how can we detect this type of mistake?  Well, there is a simple
method that will catch at least some of them.  What you need is a meta-test: a
test that ensures that the tests aren't lying to you.  It's really not that
bad; here's the pseudocode:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
for each test in the suite:
    for each line of code that isn't an assertion:
        remove that line of code (but not the rest)
        run the test and make sure that it fails
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;Basically, this meta-test is ensuring that every line of code in the test
is required: removing any line should cause the test to fail.  This sounds
complicated, but it only has to be implemented once.  Once it exists as a &lt;a
    href="http://somethingaboutorange.com/mrl/projects/nose/"&gt;nose&lt;/a&gt; plugin,
for example, you can use it without writing any extra code.&lt;/p&gt;

&lt;p&gt;Let's look at how this would affect the example.  Here's the testing code
again:&lt;/p&gt;

&lt;blockquote&gt;&lt;div class="highlight"&gt;&lt;pre&gt; 1 &lt;span style="color: #AA22FF; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #00A000"&gt;test_web_client&lt;/span&gt;():
 2     &lt;span style="color: #008800; font-style: italic"&gt;# Make sure everything works with normal HTTP&lt;/span&gt;
 3     client &lt;span style="color: #666666"&gt;=&lt;/span&gt; WebClient()
 4     &lt;span style="color: #AA22FF; font-weight: bold"&gt;assert&lt;/span&gt; client&lt;span style="color: #666666"&gt;.&lt;/span&gt;get(&lt;span style="color: #BB4444"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;) &lt;span style="color: #666666"&gt;==&lt;/span&gt; expected_data &lt;span style="color: #008800; font-style: italic"&gt;#defined elsewhere&lt;/span&gt;
 5 
 6     &lt;span style="color: #008800; font-style: italic"&gt;# Make sure everything works with SSL as well&lt;/span&gt;
 7     client&lt;span style="color: #666666"&gt;.&lt;/span&gt;use_ssl &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #AA22FF"&gt;True&lt;/span&gt;
 8     &lt;span style="color: #AA22FF; font-weight: bold"&gt;assert&lt;/span&gt; client&lt;span style="color: #666666"&gt;.&lt;/span&gt;get(&lt;span style="color: #BB4444"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;) &lt;span style="color: #666666"&gt;==&lt;/span&gt; expected_data
&lt;/pre&gt;&lt;/div&gt;&lt;/blockquote&gt;

&lt;p&gt;The meta-test will step through, removing each relevant line and making
sure that the test fails.  The only executable lines that aren't assertions
are 3 and 7.  When it removes line 3, the test will fail because "client"
won't be defined.  So that iteration of the meta-test passes.  When it removes
line 7, &lt;i&gt;the test will still pass&lt;/i&gt;.  Because the test passes with a line
removed, the meta-test will fail.  The meta-test has detected the fact that
line 7 isn't necessary, which is a red flag that says "this test might lie to
you later!"&lt;/p&gt;

&lt;p&gt;It's important to note that the meta-test will fail even when the test is
working.  It really is a &lt;i&gt;meta&lt;/i&gt;-test: it's only testing the test.  This
is a good thing.  It tells you when you've written a crappy test - a test that
isn't paying enough attention.&lt;/p&gt;

&lt;p&gt;Let's return to the example and try to fix it.  To make the meta-test pass
again, the test could be changed to be more sensitive to WebClient's
state:&lt;/p&gt;

&lt;blockquote&gt;&lt;div class="highlight"&gt;&lt;pre&gt; 1 &lt;span style="color: #AA22FF; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #00A000"&gt;test_web_client&lt;/span&gt;():
 2     &lt;span style="color: #008800; font-style: italic"&gt;# Make sure everything works with normal HTTP&lt;/span&gt;
 3     client &lt;span style="color: #666666"&gt;=&lt;/span&gt; WebClient()
 4     &lt;span style="color: #AA22FF; font-weight: bold"&gt;assert&lt;/span&gt; client&lt;span style="color: #666666"&gt;.&lt;/span&gt;get(&lt;span style="color: #BB4444"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;) &lt;span style="color: #666666"&gt;==&lt;/span&gt; expected_data &lt;span style="color: #008800; font-style: italic"&gt;#defined elsewhere&lt;/span&gt;
 5     &lt;span style="color: #f00; font-weight: bold"&gt;assert&lt;/span&gt; &lt;span style="color: #f00"&gt;client&lt;/span&gt;&lt;span style="color: #f00"&gt;.&lt;/span&gt;&lt;span style="color: #f00"&gt;use_ssl&lt;/span&gt; &lt;span style="color: #f00"&gt;==&lt;/span&gt; &lt;span style="color: #f00"&gt;False&lt;/span&gt;
 6 
 7     &lt;span style="color: #008800; font-style: italic"&gt;# Make sure everything works with SSL as well&lt;/span&gt;
 8     client&lt;span style="color: #666666"&gt;.&lt;/span&gt;use_ssl &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #AA22FF"&gt;True&lt;/span&gt;
 9     &lt;span style="color: #AA22FF; font-weight: bold"&gt;assert&lt;/span&gt; client&lt;span style="color: #666666"&gt;.&lt;/span&gt;get(&lt;span style="color: #BB4444"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;) &lt;span style="color: #666666"&gt;==&lt;/span&gt; expected_data
10     &lt;span style="color: #f00; font-weight: bold"&gt;assert&lt;/span&gt; &lt;span style="color: #f00"&gt;client&lt;/span&gt;&lt;span style="color: #f00"&gt;.&lt;/span&gt;&lt;span style="color: #f00"&gt;use_ssl&lt;/span&gt; &lt;span style="color: #f00"&gt;==&lt;/span&gt; &lt;span style="color: #f00"&gt;True&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/blockquote&gt;

&lt;p&gt;Now the meta-test passes, and the original test_web_client is more
resilient to silent failures.  If someone renames WebClient's use_ssl
attribute, the test won't silently stop testing like it did before.  Instead,
line 5 will raise an exception and the test will fail.&lt;/p&gt;

&lt;p&gt;Of course, this isn't foolproof.  If you added line 10 but not line 5, you
wouldn't be doing yourself any good (figuring out why is left as an exercise
for the reader :).  The meta-test would still pass, though, and you would
still have a test that may lie to you in the future.  So this meta-testing
method isn't a magic bullet that will force you to write good tests.  For a
careful tester, though, it throws up a red flag for tests that might be
susceptible to very subtle errors.

&lt;p&gt;(Nitpicker's corner: Yes, the problem in this test was caused by
questionable design in WebClient itself.  Using an instance variable to
control a class's behavior in this way is error-prone to begin with.  This
testing problem also arises in much more subtle situations, though; I have the
scars to prove it.)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-3669654063493988450?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/3669654063493988450/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=3669654063493988450' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/3669654063493988450'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/3669654063493988450'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/04/are-your-tests-lying-to-you.html' title='Are your tests lying to you?'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-4714820412262781022</id><published>2007-03-14T13:17:00.000-07:00</published><updated>2007-03-14T13:19:12.838-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rest'/><category scheme='http://www.blogger.com/atom/ns#' term='vb'/><category scheme='http://www.blogger.com/atom/ns#' term='ws'/><category scheme='http://www.blogger.com/atom/ns#' term='webservices'/><title type='text'>The 13-Year-Old Equivalent of WS-*</title><content type='html'>&lt;p&gt;One of the very first non-trivial programs I wrote was a game (of course),
and it's hilarious to look back on it now.  It was sort of like &lt;a
    href="http://en.wikipedia.org/wiki/Dope_Wars"&gt;Dope Wars&lt;/a&gt; meets &lt;a
    href="http://en.wikipedia.org/wiki/Choose_your_own_adventure"&gt;Choose Your
    Own Adventure&lt;/a&gt;, with a ton of 13-year-old humor thrown in.  Even though
I haven't seen it in ten years, there's one hilarious feature I remember very
well.&lt;/p&gt;

&lt;p&gt;Since it was a game (and not an &lt;a
    href="http://en.wikipedia.org/wiki/Abstract_game"&gt;abstract&lt;/a&gt;), it needed
random numbers: you could buy and sell various illicit substances, and I
needed to randomize the market prices.  After stumbling around the VB docs for
a while, I found the "Rnd" function, which returns a random float from 0 to 1.
Obviously, I could generate random prices by doing this:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;price = max_price * Rnd&lt;/blockquote&gt;&lt;/pre&gt;

&lt;p&gt;Unfortunately, I didn't realize that.  The example I saw for the "Rnd"
function showed it being used like this:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
value = Rnd
if value &lt; 0.5 then
    'do the first thing
else
    'do the second thing
end if
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;Being the inexperienced pseudo-programmer that I was, I blindly copied the
form of the example to suit my needs.  My code went something like this:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
value = Rnd
if value &lt; 0.25 then
    price = 7
else if value &lt; 0.5 then
    price = 12
else if value &lt; 0.75 then
    price = 25
else
    price = 31
end if
&lt;/blockquote&gt;&lt;/pre&gt;

&lt;p&gt;I'm pretty sure there were only four cases, but there might've been eight.
Either way, the limiting force for the number of cases was simple laziness.
Maybe if I'd been less lazy, and added more cases, I would've noticed that I
was moving toward n conditionals with a probability of 1/n each.

&lt;p&gt;I like to think about stupid things like this for two reasons.  First, it
helps me keep at least a shred of humility about my abilities.  In ten years,
the software I'm writing now will probably also look ridiculous to me (but
hopefully not &lt;i&gt;this&lt;/i&gt; ridiculous).  Second, this is a great example of
missing the elegant solution and ending up with something big and gross.  You
might say it's the 13-year-old equivalent of WS-*.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-4714820412262781022?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/4714820412262781022/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=4714820412262781022' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/4714820412262781022'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/4714820412262781022'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/03/13-year-old-equivalent-of-ws.html' title='The 13-Year-Old Equivalent of WS-*'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-4746092102622611961</id><published>2007-03-06T11:42:00.000-08:00</published><updated>2007-03-06T11:43:03.921-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sucksrocks'/><category scheme='http://www.blogger.com/atom/ns#' term='slashdot'/><category scheme='http://www.blogger.com/atom/ns#' term='search'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='web.py'/><title type='text'>Zero to Slashdot in Three Days</title><content type='html'>&lt;h4&gt;The Genesis&lt;/h4&gt;

&lt;p&gt;A few days before PyCon, &lt;a href="http://blog.case.edu/bmb12/"&gt;Brian&lt;/a&gt;
suggested that we build a web app in one night.  It took a little longer than
that to polish it up, but we launched &lt;a
    href="http://www.sucks-rocks.com/"&gt;sucks-rocks.com&lt;/a&gt; on Tuesday.  Since
then, it's had over 40,000 page views and been slashdotted (OK...  it was the
Japanese Slashdot, but it's still &lt;i&gt;a&lt;/i&gt; Slashdot.)&lt;/p&gt;

&lt;p&gt;Sucks/rocks rates the terms you enter by doing web searches and counting
results.  For example, if you search for "Windows sucks" using Google, you'll
get many more results than for "Windows rocks".  The opposite is true for
FreeBSD.  From this, we can infer that people probably like FreeBSD more than
Windows.  The actual searches that are done by sucks/rocks are more complex
than this, but they follow a similar pattern.&lt;/p&gt;

&lt;h4&gt;The Search Engine Arms Race&lt;/h4&gt;

&lt;p&gt;Once we started getting a lot of traffic, it was very hard for us to keep
sucks/rocks going because we kept running out of searches.  Here are the
search APIs we used, in the order that we added them:&lt;/p&gt;

&lt;table border="1", style="margin: 1em; border-collapse: collapse"&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;b&gt;Search Engine&lt;/b&gt;&lt;/th&gt;
        &lt;td&gt;&lt;b&gt;Queries/Day&lt;/b&gt;&lt;/td&gt;
        &lt;td&gt;&lt;b&gt;Interface&lt;/b&gt;&lt;/td&gt;
        &lt;td&gt;&lt;b&gt;Suckiness of Results&lt;/b&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;Google&lt;/td&gt;
        &lt;td&gt;1,000&lt;/td&gt;
        &lt;td&gt;SOAP&lt;/td&gt;
        &lt;td&gt;Low&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;Yahoo&lt;/td&gt;
        &lt;td&gt;5,000&lt;/td&gt;
        &lt;td&gt;REST&lt;/td&gt;
        &lt;td&gt;Pretty low&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;live.com&lt;/td&gt;
        &lt;td&gt;10,000&lt;/td&gt;
        &lt;td&gt;SOAP&lt;/td&gt;
        &lt;td&gt;IMMEASURABLY HIGH!&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;We started with Google, but ran out of queries before we even launched.  We
then used Yahoo, but ran out when 100shiki.com &lt;a
    href="http://www.100shiki.com/archives/2007/03/sucksrocks.html"&gt;linked&lt;/a&gt;
to us, forcing me to add support for live.com.  Unfortunately, live.com's search
results are terrible. &lt;b&gt;Terrible!&lt;/b&gt; If you &lt;a
    href="http://www.sucks-rocks.com/"&gt;search&lt;/a&gt; sucks/rocks for "lord of the
rings", you'll get a "?" back.  This means that the engine whose results are
cached (which is live.com, of course) reported that there were 0 "total
results available".  Great.&lt;/p&gt;

&lt;p&gt;Now we have a cache of almost 60,000 searches, most of which are from
live.com.  Many of those are totally wrong, of course.  My next task is to add
a background thread that slowly replaces all of the cached live.com results
with Yahoo results.&lt;/p&gt;

&lt;h4&gt;The Code&lt;/h4&gt;

&lt;p&gt;Sucks/rocks runs on top of &lt;a href="http://webpy.org"&gt;web.py&lt;/a&gt;, but only
uses it for URL dispatching.  &lt;a href="http://pythonpaste.org/"&gt;Paste&lt;/a&gt;
does the HTTP serving, with &lt;a
    href="http://www.webfaction.com?affiliate=grb"&gt;WebFaction&lt;/a&gt;'s Apache
instance on the front end (disclaimer: the WebFaction link is an affiliate
link).  This simple setup handled about a million HTTP requests in four days,
using less than 5% of the CPU almost all of the time (except when it was at
the top of slashdot.jp).&lt;/p&gt;

&lt;a href="http://www.flickr.com/photos/7135305@N02/410706954/"&gt;&lt;img
    src="http://farm1.static.flickr.com/152/410706954_5c5a81ade5_o.png"
    width="345" height="220" alt="Sucks/Rocks Traffic" style="float: right"
    /&gt;&lt;/a&gt;

&lt;h4&gt;Easy Come, Easy Go&lt;/h4&gt;

&lt;p&gt;With our slashdotting over, We've gone from 10,000+ pageviews per day to
about 1,000.  Slashdot giveth, and Slashdot taketh away.  That's OK, because I
need some time to push all of the crappy live.com results out of the cache
anyway.&lt;/p&gt;

&lt;p&gt;(Brian has also posted about sucks/rocks: &lt;a
    href="http://blog.case.edu/bmb12/2007/02/sucksrocks_does_it_suck"&gt;1&lt;/a&gt;,
&lt;a href="http://blog.case.edu/bmb12/2007/03/japan_loves_sucksrocks"&gt;2&lt;/a&gt;).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-4746092102622611961?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/4746092102622611961/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=4746092102622611961' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/4746092102622611961'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/4746092102622611961'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/03/zero-to-slashdot-in-three-days.html' title='Zero to Slashdot in Three Days'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-5848795041777572001</id><published>2007-02-27T19:43:00.000-08:00</published><updated>2007-03-06T11:30:35.124-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='pycon'/><category scheme='http://www.blogger.com/atom/ns#' term='pycon2007'/><title type='text'>PyCon 2007: The Untold Stories</title><content type='html'>&lt;p&gt;Most of the PyCon &lt;a
    href="http://www.technorati.com/search/%22pycon+2007%22"&gt;posts&lt;/a&gt; are
about the sessions, so here are some of the interesting things I did outside
of the scheduled talks.  I have pictures for many of them thanks to &lt;a
    href="http://exilejedi.livejournal.com/"&gt;Mike Pirnat&lt;/a&gt;'s diligent
photography.&lt;/p&gt;

&lt;a href="http://www.flickr.com/photos/mikepirnat/404128129/"&gt;&lt;img
    src="http://farm1.static.flickr.com/188/404128129_d23e46ed47_m.jpg"
    style="clear: right; float: right" /&gt;&lt;/a&gt;

&lt;h4&gt;Pagoda CMS&lt;/h4&gt;

&lt;p&gt;&lt;a href="http://blog.case.edu/bmb12/"&gt;Brian&lt;/a&gt;, &lt;a
    href="http://blog.case.edu/csh11/"&gt;Chris&lt;/a&gt;, and &lt;a
    href="http://www.iancharnas.com/"&gt;Ian&lt;/a&gt; demoed &lt;a
    href="http://www.pagodacms.org/"&gt;Pagoda&lt;/a&gt;, their upcoming open-source
CMS.  It's very user-centric, and they're spending a lot of effort on the user
experience.  Even though I don't use CMSes, I'm excited about this project
because I'm so sick of crappy UIs.  Peoples' responses seemed positive, but I
think some people were disappointed that Pagoda takes the easy-to-use approach
rather than the kitchen-sink approach.  That's ok; that's why we have Zope -
the kitchen sink is there for the taking!&lt;/p&gt;

&lt;h4&gt;Python Is Basically DOS, Right?&lt;/h4&gt;

&lt;p&gt;I headed up to my room to grab my hoodie, and on the way back I was in the
elevator with a 40ish couple.  They asked me what this conference was about; I
told them it was about Python, which is a programming language.  The guy asked
me whether "that's anything like DOS".  It was kind of funny, but mostly just
jarring.  After being in close quarters with lots of smart programmers for 2
days, it was weird to suddenly talk to someone whose computer experience
apparently began and ended around 1990.&lt;/p&gt;

&lt;a href="http://www.flickr.com/photos/mikepirnat/401971587"&gt;&lt;img
    src="http://farm1.static.flickr.com/165/401971587_2ec1d0a791_m.jpg"
    style="clear: right; float: right" /&gt;&lt;/a&gt;

&lt;h4&gt;The Mysterious Ellipsis&lt;/h4&gt;

&lt;p&gt;&lt;a href="http://www.traceback.org/"&gt;Dave&lt;/a&gt;, &lt;a
    href="http://exilejedi.livejournal.com/"&gt;Mike&lt;/a&gt;, and I were at the
hotel's bar, and the topic of Python's ellipsis operator ("...") came up some
how.  From the grammar in the &lt;a
    href="http://docs.python.org/ref/slicings.html"&gt;slicing docs&lt;/a&gt;, we could
tell that the ellipsis could appear in slices, but we couldn't trick Python
into taking it without throwing an exception.  I figured it out later - in a
slice, the "..." token is just translated into an "Ellipsis" object:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
&amp;gt;&amp;gt;&amp;gt; class Foo:
...     def __getitem__(self, x):    
...         return x
... 
&amp;gt;&amp;gt;&amp;gt; f = Foo()
&amp;gt;&amp;gt;&amp;gt; f[1:2:3]
slice(1, 2, 3)
&amp;gt;&amp;gt;&amp;gt; f[...]
Ellipsis
&amp;gt;&amp;gt;&amp;gt; f[1, 2, ..., 100]
(1, 2, Ellipsis, 100)
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;Apparently, it's mostly used for numeric stuff like &lt;a
    href="http://numpy.scipy.org/"&gt;Numpy&lt;/a&gt;.  I definitely understand
Python's slicing much better after that confusing night.&lt;/p&gt;

&lt;a href="http://www.flickr.com/photos/7135305@N02/405265587/"&gt;&lt;img
    src="http://farm1.static.flickr.com/139/405265587_35f9b3ce30_m.jpg"
    style="clear: right; float: right" /&gt;&lt;/a&gt;

&lt;h4&gt;Mischief on The Open Space Board&lt;/h4&gt;

&lt;p&gt;&lt;a href="http://blog.case.edu/bmb12/"&gt;Brian&lt;/a&gt; and &lt;a
    href="http://blog.case.edu/csh11/"&gt;Chris&lt;/a&gt; posted a "Python in The Adult
Entertainment Industry" card on the open spaces board with my name on it.  It
was up for about 20 minutes before Brian pointed it out to me and I took it
down.  Hopefully, I escaped without too many prominent Python hackers
associating me with pornography.&lt;/p&gt;

&lt;p&gt;I have to wonder whether anyone saw that card and was actually interested
in going to the session.  Maybe Chris and Brian's silliness prompted an
interesting discussion of Python and porn somewhere...&lt;/p&gt;

&lt;a href="http://www.flickr.com/photos/mikepirnat/404139318/"&gt;&lt;img
    src="http://farm1.static.flickr.com/173/404139318_c77eff3d79_m.jpg"
    style="clear: right; float: right" /&gt;&lt;/a&gt;

&lt;h4&gt;RESTDB&lt;/h4&gt;

&lt;p&gt;The open space I actually did lead was on "REST, Databases, and RESTful
Databases" rather than pornography.  Unfortunately, I dove into explaining &lt;a
    href="http://blog.extracheese.org/2007/02/introducing-another-wildly-ambitious.html"&gt;RESTDB&lt;/a&gt;
right at the start.  It turned out that not everyone was familiar with REST,
or convinced of its usefulness, or both.  So, we ended up talking about REST
for the second half.  I think the session would've been more useful to
everyone involved if we'd discussed REST first, then moved on to RESTful
databases.  I'm not sure how much everyone else got out of it, but I learned a
lot about how to explain what RESTDB is and why we might want it.&lt;/p&gt;

&lt;h4&gt;Django vs. The World&lt;/h4&gt;

&lt;p&gt;I didn't fly back until Monday, so I was still there on Sunday night.  Most
of the people who were still there were staying for the sprints, so the
conference area was pretty quiet as everyone quietly hacked away (with the
exception of the Wii room).&lt;/p&gt;

&lt;a href="http://www.flickr.com/photos/mikepirnat/404132325/"&gt;&lt;img
    src="http://farm1.static.flickr.com/160/404132325_a547a518df_m.jpg"
    style="clear: right; float: right" /&gt;&lt;/a&gt;

&lt;p&gt;I was on my way back to the "quiet room", which was full of Django guys.
On my way there, a big group of people appeared and asked where the Django
guys were.  I pointed them towards the quiet room and joined them on their way
there.  The group was made up of &lt;a
    href="http://www.turbogears.org/"&gt;TurboGears&lt;/a&gt; guys, &lt;a
    href="http://pylonshq.com/"&gt;Pylons&lt;/a&gt; guys, &lt;a
    href="http://pythonpaste.org/"&gt;Paste&lt;/a&gt; guys, and some that I didn't
recognize.  They busted into the Django room and caused some friendly
commotion, with one notable result being &lt;a
    href="http://blog.ianbicking.org/python-packaging.html"&gt;this post&lt;/a&gt; on
Ian Bicking's blog.  I'm pretty sure that EWT's bathtub full of alcohol
(pictured to the right) was a factor in this incident.&lt;/p&gt;

&lt;h4&gt;Magic URL Mapping&lt;/h4&gt;

&lt;p&gt;After the ruckus in the quiet room ended, I hacked up some crazy URL
mapping code based on an &lt;a
    href="http://blog.case.edu/bmb12/2005/12/python_web_framework_experiments_day_1"&gt;experiment&lt;/a&gt;
Brian did a while back.  Here's a controller defined using it:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
class UserController(_/'users'/User):
    def get(self, user):
        return dict(
            email=user.email,
            name=user.name)
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;The &lt;i&gt;_/'users'/User&lt;/i&gt; part defines the controller's URL, and
&lt;i&gt;User&lt;/i&gt; is actually a RESTDB resource type.  So, for example, if someone
requests &lt;i&gt;/users/Bob&lt;/i&gt;, this controller will be invoked and the Bob RESTDB
resource will automatically be retrieved and passed in.  This works for
multiple records, so you could also have more complex controllers like:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
class BlogController(_/'users'/User/'blogs'/Blog):
    def get(self, user, blog):
        assert blog.user == user # yep!
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;&lt;i&gt;BlogController&lt;/i&gt; would be called for URLs like
&lt;i&gt;/users/Bob/blogs/TheBobBlog&lt;/i&gt; and, once again, both Bob and his blog
would automatically be pulled out of the database.  Of course, it's fully
RESTful (hence the &lt;i&gt;get&lt;/i&gt; method).&lt;/p&gt;

&lt;p&gt;Keep in mind that this is just a silly experiment; please don't freak out
because I'm overloading division to produce a URL mapping object that I then
subclass.  (Although, to be honest, the code isn't that bad; it's only about
60 lines long.)&lt;/p&gt;

&lt;p&gt;Overall, PyCon was awesome, and I'm really glad I went.  It's going to be
in Chicago next year, so I won't have to lose two full days to travel
(awesome!)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-5848795041777572001?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/5848795041777572001/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=5848795041777572001' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/5848795041777572001'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/5848795041777572001'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/02/pycon-2007-untold-stories.html' title='PyCon 2007: The Untold Stories'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://farm1.static.flickr.com/188/404128129_d23e46ed47_t.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-2109088217584648164</id><published>2007-02-17T21:53:00.001-08:00</published><updated>2007-02-18T07:46:05.146-08:00</updated><title type='text'>Introducing Another Wildly Ambitious Database Project</title><content type='html'>&lt;p&gt;One week ago today, I started hacking on a new project: a database
implemented as a RESTful HTTP service.  &lt;a
    href="http://blog.case.edu/bmb12/"&gt;Brian&lt;/a&gt; has been pestering me to post
about it since before any code was even written, so here we are.&lt;/p&gt;

&lt;p&gt;I've been calling the project RESTDB, but that's only because I haven't
come up with a better name yet.  It's sort of relational, but not quite.
Depending on how you squint, you might think it is.  Likewise, it's not
completely RESTful: it lacks arbitrary POST semantics.  Despite these caveats,
it's quite similar to both RDBMSes and RESTful systems.  Let's have a
look.&lt;/p&gt;

&lt;b&gt;Defining the Schema&lt;/b&gt;

&lt;p&gt;We're going to build a multi-user TODO list (basically stolen from &lt;a
    href="http://exogen.case.edu/turbogears.html"&gt;Brian's TurboGears
    tutorial&lt;/a&gt;).  As with a traditional RDBMS, the first step is to define
our schema:

&lt;blockquote&gt;&lt;pre&gt;
class User(Resource):
    email = String(key=True)
    lists = List(Link('TodoList'))

class TodoList(Resource):
    id = Integer(key=True)
    title = String()
    items = List(Link('Item'))

class Item(Resource):
    id = Integer(key=True)
    value = String()
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;Obviously, this looks a lot like a &lt;a
    href="http://www.sqlobject.org/"&gt;SQLObject&lt;/a&gt; table definition.  One
thing is very different, though: the way resources are related to each other.
In SQLObject, each TodoList would have a foreign key that points to its User.
In RESTDB, this is inverted: the User contains a list of links that point to
TodoLists.  In SQL terms, you can think of this as a list of foreign keys.
This has many serious implications, both for the database's implementation and
for how clients interact with it.  In the interest of brevity, I will
valiantly gloss over every single one of them for now.&lt;/p&gt;

&lt;b&gt;The Data&lt;/b&gt;

&lt;p&gt;Now that we've defined our schema, let's see what's going on from an HTTP
perspective.  The definition above will lead to a URL structure like this
(with arbitrary example records inserted):&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
/User
/User/me@example.com
/TodoList
/TodoList/1
/Item
/Item/1
/Item/2
/Item/3
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;These resources' structures are dictated by the resource classes we defined
above.  Resources are stored as simple &lt;a href="http://www.json.org/"&gt;JSON&lt;/a&gt;
data, so they're human readable even in their raw form.  For example, here's
what "/TodoList/1" might look like:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
{
    'id': 1,
    'title': 'Groceries',
    'items': [
        '/Item/1',
        '/Item/2',
        '/Item/3',
    ]
}
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;It's just plain old JSON data, but it follows our schema: there's an ID, a
title, and a list of item links.  You don't even need a database client
program to look at it; just point your web browser at the resource and you'll
get back the JSON representation.  You can download the whole database with
&lt;i&gt;wget&lt;/i&gt; if you want to.&lt;/p&gt;

&lt;b&gt;The Client&lt;/b&gt;

&lt;p&gt;Of course, this database isn't designed to be used by humans directly;
human readability is just a nice bonus.  wgetting your database is a neat
gimmick, but what we really care about manipulating it with code.&lt;/p&gt;

&lt;p&gt;To illustrate how simple the client is, here's the entire client-side
definition for our todo list database:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
c = Client('127.0.0.1:17321')
User = Resource(c, 'User')
TodoList = Resource(c, 'TodoList')
Item = Resource(c, 'Item')
&lt;/blockquote&gt;&lt;/pre&gt;

&lt;p&gt;That's it: all you have to do is tell it the names of the resources.  Note
that this doesn't mean that there aren't constraints on the data - there are!
Lots of constraints - all the constraints you care to define!  It's just that
they're only on the server side.  If you step out of line, the server will
slap you with an "HTTP 400: No Shenanigans Allowed".&lt;/p&gt;

&lt;p&gt;We'll get to the shenanigans in a minute.  First, let's try the client out
by creating a user:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
&amp;gt;&amp;gt&amp;gt; me = User.post(email="me@example.com", lists=[])
&amp;gt;&amp;gt&amp;gt; me.email
u'me@example.com'
&amp;gt;&amp;gt&amp;gt; me.lists
[]
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;All we do is POST a new user resource with an email address and no todo
lists.  This is literally just an HTTP POST to /User.  The database responds
with an HTTP "Location" header to tell the client that the new resource is at
"/User/me@example.com".&lt;/p&gt;

&lt;p&gt;Now that our user is securely fastened to the database, let's create a todo
list with some items:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
&amp;gt;&amp;gt&amp;gt; # Create a todo list and assign it to the user
&amp;gt;&amp;gt&amp;gt; my_list = TodoList.post(title="Groceries", items=[])
&amp;gt;&amp;gt&amp;gt; me.lists.append(my_list)
&amp;gt;&amp;gt&amp;gt; me.put()

&amp;gt;&amp;gt&amp;gt; # Create some items and add them to the todo list
&amp;gt;&amp;gt&amp;gt; i1 = Item.post(id=0, value="Milk")
&amp;gt;&amp;gt&amp;gt; i2 = Item.post(id=1, value="Eggs")
&amp;gt;&amp;gt&amp;gt; i3 = Item.post(id=2, value="Bread")
&amp;gt;&amp;gt&amp;gt; my_list.items += [i1, i2, i3]
&amp;gt;&amp;gt&amp;gt; my_list.put()
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;We POST a new TodoList, just like we POSTed a new user before.  Then we
have to update the user resource to point at the new list.  &lt;i&gt;me.lists&lt;/i&gt; is
just a plain old Python list, so we append the new todo list, then PUT
&lt;i&gt;me&lt;/i&gt; to update the server's copy.  We then repeat the same process to add
items to the todo list.&lt;/p&gt;

&lt;b&gt;Light's Green; Trap's Clean&lt;/b&gt;

&lt;p&gt;Now that we've trapped a bunch of data in our database, let's start from
scratch and pull the todo list's items back:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
&amp;gt;&amp;gt&amp;gt; me = User.get("me@example.com")
&amp;gt;&amp;gt&amp;gt; print [item.value for item in me.lists[0].items]
[u'Milk', u'Eggs', u'Bread']
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;Awesome.&lt;/p&gt;

&lt;p&gt;But wait, I've conveniently left a loose end untied!  I claimed that
shenanigans were strictly forbidden.  So far, we've been acting nice and
giving the server exactly what it wants.  Now let's try to feed the server
some crap:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
&amp;gt;&amp;gt&amp;gt; me = User.post(email="you@example.com", lists=123)
Traceback (most recent call last):
  ...
client.BadRequestError: "123" is not a list
&amp;gt;&amp;gt&amp;gt; me = User.post(screw_you_server="EXPLODE!")
Traceback (most recent call last):
  ...
client.BadRequestError: Didn't expect field "screw_you_server"
&lt;/blockquote&gt;&lt;/pre&gt;

&lt;p&gt;It's having none of it!  It will snub your stupidly-formed data all day
long.  And it's not just simple things like types that are enforced.  You can
define regex constraints for your strings, ranges for your numbers, and
whatever else you can dream up.  You can suffocate your precious data with
constraints.  Your links are guaranteed to reference valid resources; your
URLs are guaranteed to match your data; your data is guaranteed to match your
schema.&lt;/p&gt;

&lt;p&gt;Everything I've shown here is real, working code.  A few of the things I've
mentioned, like link validity constraints, aren't done yet, but they're
coming.  Unfortunately, I can't point you at subversion just yet, because I
don't have anywhere to host it.  That will hopefully change soon, and you'll
be able to prod it for yourself.  For now, you'll have to make do with
imagining how awesome it would be to speed up your database by sticking a
plain old HTTP caching proxy in front of it.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-2109088217584648164?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/2109088217584648164/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=2109088217584648164' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/2109088217584648164'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/2109088217584648164'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/02/introducing-another-wildly-ambitious.html' title='Introducing Another Wildly Ambitious Database Project'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-8482306170777822384</id><published>2007-02-14T10:57:00.000-08:00</published><updated>2007-02-14T11:16:36.863-08:00</updated><title type='text'>Unicode Weirdness</title><content type='html'>&lt;p&gt;I'm writing some tests to verify that BitBacker doesn't explode if it sees unicode filenames.  For a while, I thought that OS X's terminal wasn't unicode-aware, because non-ASCII unicode characters just showed up as "?":&lt;/p&gt;
&lt;blockquote&gt;&lt;pre&gt;grbmbp:~ grb$ ls z*
z???       z??????    z????????? z???       z???&lt;/blockquote&gt;&lt;/pre&gt;
&lt;p&gt;Then I happened to pipe the ls through a grep, and the unicode characters printed correctly:&lt;/p&gt;
&lt;blockquote&gt;&lt;pre&gt;grbmbp:~ grb$ ls z* | grep '.*'
z໐
z두
z툃
z䌨
z冕
&lt;/pre&gt;&lt;/blockquote&gt;
&lt;p&gt;What?  Well, I guess I'll take it...&lt;/p&gt;

&lt;p&gt;While posting this, it got even more fun.  All five of the characters above print normally in the terminal and Finder, but only two print normally in Opera's text edit control.  I wonder how many will show up once this is published.  Can you see them in your RSS reader and/or browser?&lt;/p&gt;

&lt;p&gt;Update: After publishing, I viewed the page in Safari and the characters displayed exactly like they did in the terminal and Finder.  So at least Opera didn't mangle the bytes.  However, Firefox draws the first three incorrectly (the same three that Opera couldn't draw at all).  Unsurprisingly, IE7 can't draw any of them.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-8482306170777822384?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/8482306170777822384/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=8482306170777822384' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/8482306170777822384'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/8482306170777822384'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/02/unicode-weirdness.html' title='Unicode Weirdness'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-2596761754525928607</id><published>2007-02-07T13:25:00.000-08:00</published><updated>2007-02-07T13:47:56.029-08:00</updated><title type='text'>Mutable State Is Manual Memory Management</title><content type='html'>&lt;p&gt;&lt;a href="http://sequence.complete.org/"&gt;The Haskell Sequence&lt;/a&gt; linked to my &lt;a href="http://garybernhardt.blogspot.com/2007/01/c-30-looks-promising.html"&gt;post on C#&lt;/a&gt; (although they obviously don't share my opinions on it).  I'd never read that site before, and I came across this "quote of the week" while glancing over it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Mutable state is actually another form of manual memory management: every time you over-write a value you are making a decision that the old value is now garbage, regardless of what other part of the program might have been using it."&lt;/p&gt;
&lt;p&gt;(Paul Johnson via &lt;a href="http://sequence.complete.org/"&gt;The Haskell Sequence&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
My first thought upon reading this was "why the hell didn't I think of that?"  It's obvious in retrospect, and is a wonderfully concise explanation of why side effects are a bad idea.  I always try to limit my use of side effects, but I never had a simple way to explain to myself (or others) why that's a good thing.  Thanks, Paul.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-2596761754525928607?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/2596761754525928607/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=2596761754525928607' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/2596761754525928607'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/2596761754525928607'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/02/mutable-state-is-manual-memory.html' title='Mutable State Is Manual Memory Management'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-4148463913267993684</id><published>2007-02-06T11:31:00.000-08:00</published><updated>2007-02-14T13:11:13.330-08:00</updated><title type='text'>Measure the Goodness of Your Code</title><content type='html'>&lt;p&gt;Because I want to keep my code short, I like to see statistics on my sandbox before committing to subversion.  At first, I just did things like "&lt;code&gt;svn diff | grep '^+' | wc -l&lt;/code&gt;", but that got old fast.  So, I wrote a little script called gn (for "goodness") that computes some simple statistics.  Here's what it says about my sandbox right now:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
grbmbp:~/trunk grb$ gn
591 lines of diff
129 lines added
186 lines removed
-57 lines net change
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;I've added 129 lines and removed 186, which is a net change of -57.  Any line that's simply replaced will result in one line removed and one added, for a net change of 0.  The implication here is that the more negative your "net change" is, the better.  (DISCLAIMER: Please don't take this literally and post angry comments.)&lt;/p&gt;

&lt;p&gt;Sometimes I want to know the goodness of a bunch of related changes, so the script can also take any arguments that "svn diff" can take.  It just passes them on, so you can compute statistics across revisions, etc.:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;
grbmbp:~/trunk grb$ gn -r420:451 
4246 lines of diff
1099 lines added
1627 lines removed
-528 lines net change
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;As you can see, I've been killing a lot of code recently.  The script is below, in case you want to try it for yourself.  I've only tested it on OS X; YMMV.

&lt;blockquote&gt;&lt;pre&gt;
#!/usr/bin/python
import sys, os, re

svn_args = ' '.join(sys.argv[1:])
pipe = os.popen('svn diff %s' % svn_args)
diff_lines = pipe.readlines()

# Added lines start with '+' (but not '+++', because that marks a new file).
# The same goes for removed lines, except '-' instead of '+'.
added_lines = [line for line in diff_lines
    if line.startswith('+') and not line.startswith('+++')]
removed_lines = [line for line in diff_lines
    if line.startswith('-') and not line.startswith('---')]

print '%i lines of diff' % len(diff_lines)
print '%i lines added' % len(added_lines)
print '%i lines removed' % len(removed_lines)
print '%+i lines net change' % (len(added_lines) - len(removed_lines))
&lt;/pre&gt;&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-4148463913267993684?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/4148463913267993684/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=4148463913267993684' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/4148463913267993684'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/4148463913267993684'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/02/measure-goodness-of-your-code.html' title='Measure the Goodness of Your Code'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-6078857598061198481</id><published>2007-01-24T11:38:00.000-08:00</published><updated>2007-01-24T13:17:31.071-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><category scheme='http://www.blogger.com/atom/ns#' term='languages'/><category scheme='http://www.blogger.com/atom/ns#' term='codemash'/><title type='text'>C# 3.0 Looks Promising</title><content type='html'>&lt;p&gt;At &lt;a href="http://www.codemash.org/"&gt;CodeMash&lt;/a&gt; last week, I learned
quite a bit about C#'s new features from &lt;a
    href="http://weblogs.asp.net/scottgu/"&gt;Scott Guthrie&lt;/a&gt;.  I'm no fan of
explicitly typed languages , but C# 3.0 has me very excited.  It's getting a
host of new features inspired by other languages, including anonymous types,
type inference, and lambda expressions.  There's plenty of &lt;a
    href="http://blogs.tedneward.com/2005/09/22/Language+Innovation+C+30+Explained.aspx"&gt;discussion&lt;/a&gt;
of these around the web, so I'm not going to rehash them all.  I want to
highlight the two features that I'm most excited about: type inference and
LINQ.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Type Inference&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;C# 3.0 adds basic type inference, but it's quite weak and can only infer
types on assignment.  Basically, you can do these types of things:&lt;/p&gt;
&lt;blockquote&gt;&lt;pre&gt;
    var i = 5;
    var s = some_function_returning_a_string();
    foreach (var s in list_of_strings) { ... }
&lt;/pre&gt;&lt;/blockquote&gt;
&lt;p&gt;Note that the "var" keyword does &lt;i&gt;not&lt;/i&gt; make the variables dynamically
typed - it just tells the compiler to infer their types based on what's being
assigned to them.  This is nice, but it's very limited.  You can't, for
example, declare a function with a return type of "var" and expect the
compiler to figure it out.  Haskell this definitely is not, but it's a huge
step forward for C's verbosity-laden children.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;LINQ&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Let me get this out of the way: LINQ is awesome and I want it in my
language.&lt;/p&gt;
&lt;p&gt;In a nutshell, LINQ allows you to use declarative, SQLesque syntax in your
C# code.  I've been wishing for this feature for a long time, but Python's
list comprehensions have had to hold me over so far.  Here's a very simple
example of what you can do with LINQ:&lt;/p&gt;
&lt;blockquote&gt;&lt;pre&gt;
    int[] numbers = {5, 4, 1, 9, 8};
    var low_nums = from n in numbers
                   where n &amp;lt; 5
                   select n;
&lt;/pre&gt;&lt;/blockquote&gt;
&lt;p&gt;Here's an equivalent example in Python:&lt;/p&gt;
&lt;blockquote&gt;&lt;pre&gt;
    numbers = [5, 4, 1, 9, 8]
    low_nums = [n for n in numbers if n &amp;lt; 5]
&lt;/pre&gt;&lt;/blockquote&gt;
&lt;p&gt;LINQ is far more powerful than Python's list comprehensions, though.  It
has many of the features of SQL: joins, grouping, count() and sum(), etc.  And
a single LINQ expression can query a collection of objects, a database, or XML
data.  This makes me drool.&lt;/p&gt;

&lt;p&gt;Most of the discussion of LINQ at &lt;a
    href="http://www.codemash.org"&gt;CodeMash&lt;/a&gt; boiled down to excitement over
the idea of writing statically-typed, compiler-checked database queries.  You
can probably guess that this is not the reason I'm excited.  For most
interesting problems, the limiting factor is not whether you get your queries
correct or not; it's whether you can coax your brain into (1) solving the
problem and (2) translating the solution into code.  Any feature that
simplifies your code makes step (2) easier, and LINQ is definitely such a
feature.  LINQ is the only feature of &lt;i&gt;any&lt;/i&gt; explicitly-typed language
that I am covetous of.  It really is that awesome, and Microsoft has managed
to beat every other language to the punch with this.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Back to Reality&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;OK, enough gushing.  I still dislike explicitly typed languages.  These new
features certainly aren't going to get me to leave dynamic languages behind -
C# is still incredibly verbose when compared to Python, Ruby, etc.  And, even
though many of these features were borrowed from functional and dynamic
languages, C# 3.0 is still as statically typed as ever.  However, I have a
newfound respect for &lt;a
    href="http://en.wikipedia.org/wiki/Anders_Hejlsberg"&gt;Anders Hejlsberg&lt;/a&gt;
and friends.  If Microsoft manages to drag the hoards of C# programmers toward
a more concise coding style, the world will be a much better place.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-6078857598061198481?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/6078857598061198481/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=6078857598061198481' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/6078857598061198481'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/6078857598061198481'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2007/01/c-30-looks-promising.html' title='C# 3.0 Looks Promising'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-3134201596679162414</id><published>2006-12-23T17:06:00.000-08:00</published><updated>2006-12-23T17:42:21.499-08:00</updated><title type='text'>Why dynamic typing is useful</title><content type='html'>&lt;p&gt;Steve Yegge's &lt;a href="http://steve-yegge.blogspot.com/2006/12/parabola.html"&gt;Parabola&lt;/a&gt; has thrown a little more fuel on the static vs. dynamic fire.  The comments so far have been mostly constructive, which is a nice surprise. However, there has been some back-and-forth about dynamic typing, mostly concerning why it's useful and what it actually entails.  In a recent comment, "Matt" said&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;"I don't see how software could possibly attempt to handle anything it did not anticipate, except to gracefully fail, which T.S. did."&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;I've seen this sentiment before, almost always from people whose programming experience is limited to statically typed languages. If that's your background, then you've not seen the wonderful flexibility that dynamic typing adds. Here's a very simple example written in Python.  Don't worry - Python is called "executable pseudo code" for a reason!&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;def process_many(things):
    for thing in things:
        thing.process()&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;Looking at this, you might expect &lt;span style="font-style: italic;"&gt;things&lt;/span&gt; to be a list.  That's possible, but it could also be a set, or an iterator, or even a dictionary (a hash table).  As the programmer, you don't have to worry about it.  As long as Python can figure out how to iterate over your &lt;span style="font-style: italic;"&gt;things&lt;/span&gt; argument, everything will just work.&lt;/p&gt;

&lt;p&gt;So, to return to Matt's statement above: this is one way code can handle situations that the programmer never considered.  Maybe the guy who wrote &lt;span style="font-style: italic;"&gt;process_many&lt;/span&gt; never even thought that someone might pass in a dictionary.  That doesn't matter; it will work anyway.&lt;/p&gt;

&lt;p&gt;Of course, you can get this effect in Java using interfaces.  The problem with interfaces is that you have to make decisions about them ahead of time.  If you were writing &lt;span style="font-style: italic;"&gt;process_many&lt;/span&gt;&lt;span style="font-style: italic;"&gt;&lt;/span&gt; in Java, you might not consider the case of passing in anything but a list. Then, if someone using your code wanted to pass in a set, iterator, dictionary, etc., they'd be out of luck.&lt;/p&gt;

&lt;p&gt;This is a recurring theme with static vs. dynamic languages. In explicitly typed static languages, you have to spend time figuring out what type every little thing should have.  Then, when you're finally done, there will always be some case you didn't consider, like a hand-written note telling you to call the ticket counter.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-3134201596679162414?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/3134201596679162414/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=3134201596679162414' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/3134201596679162414'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/3134201596679162414'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2006/12/why-dynamic-typing-is-useful.html' title='Why dynamic typing is useful'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6753084968628985846.post-4810333049946701999</id><published>2006-12-19T20:09:00.000-08:00</published><updated>2006-12-19T20:50:55.324-08:00</updated><title type='text'>Python's default arguments are tricky</title><content type='html'>&lt;p&gt;Default arguments are evaluated at function definition time, so they're persistent across calls. This has some interesting (and confusing) side effects.  An example:&lt;/p&gt;&lt;pre&gt;&amp;gt;&amp;gt;&amp;gt; def foo(d=[]):
...     d.append('a')
...     return d&lt;/pre&gt;&lt;p&gt;If you've not tried this before, you probably expect foo to always return ['a']: it should start with an empty list, append 'a' to it, and return.  Here's what it actually does:&lt;/p&gt;&lt;pre&gt;&amp;gt;&amp;gt;&amp;gt; foo()
['a']
&amp;gt;&amp;gt;&amp;gt; foo()
['a', 'a']
&amp;gt;&amp;gt;&amp;gt; foo()
['a', 'a', 'a']&lt;/pre&gt;&lt;p&gt;This is because the default value for &lt;em&gt;d&lt;/em&gt; is allocated when the function is created, not when it's called.  Each time the function is called, the value is still hanging around from the last call.  This gets even weirder if you throw threads into the mix.  If two different threads are executing the function at the same time, and one of them changes a default argument, they both will see the change.&lt;/p&gt;&lt;p&gt;Of course, all of this is only true if the default argument's value is a mutable type.  If we change &lt;em&gt;foo&lt;/em&gt; to be defined as&lt;/p&gt;&lt;pre&gt;&amp;gt;&amp;gt;&amp;gt; def foo2(d=0):
...     d += 1
...     return d&lt;/pre&gt;&lt;p&gt;then it will always return 1.  (The difference here is that in &lt;em&gt;foo2&lt;/em&gt;, the variable &lt;em&gt;d&lt;/em&gt; is being reassigned, while in &lt;em&gt;foo&lt;/em&gt; its value was being changed.)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6753084968628985846-4810333049946701999?l=garybernhardt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://garybernhardt.blogspot.com/feeds/4810333049946701999/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6753084968628985846&amp;postID=4810333049946701999' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/4810333049946701999'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6753084968628985846/posts/default/4810333049946701999'/><link rel='alternate' type='text/html' href='http://garybernhardt.blogspot.com/2006/12/default-arguments-are-tricky.html' title='Python&apos;s default arguments are tricky'/><author><name>Gary Bernhardt</name><uri>http://www.blogger.com/profile/12660928207535048878</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry></feed>
