<?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-699020937694156193</id><updated>2012-02-16T10:52:01.385-05:00</updated><category term='ruby'/><category term='scala'/><category term='emacs'/><category term='javascript'/><category term='java'/><category term='personal'/><category term='clojure'/><category term='mongo'/><category term='erlang'/><category term='ajax'/><category term='programming'/><category term='10gen'/><category term='games'/><category term='music'/><category term='midi'/><category term='cloud'/><category term='osx'/><category term='firefox'/><category term='audio'/><category term='edit'/><category term='filesystem'/><category term='css'/><category term='text'/><category term='frameworks'/><category term='opensource'/><category term='shell'/><category term='unix'/><category term='rails'/><category term='video'/><category term='email'/><category term='xhtml'/><category term='testing'/><category term='c++'/><category term='science'/><category term='kids'/><category term='google'/><category term='database'/><category term='humor'/><title type='text'>Shiny Things</title><subtitle type='html'>Jim Menard likes Shiny Things. Technology is shiny.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>41</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-8990814544465902382</id><published>2011-06-04T08:11:00.002-04:00</published><updated>2011-06-04T08:18:13.513-04:00</updated><title type='text'>Identity Recall</title><content type='html'>&lt;p&gt;&lt;a href="http://www.io.com"&gt;io.com&lt;/a&gt; is going away. It's funny how
upsetting this is to me. I've
been &lt;a href="mailto:jimm@io.com"&gt;jimm@io.com&lt;/a&gt; since 1994 or
so&amp;mdash;maybe a year or two earlier than that. You know what I'm worried
about most? All those open source projects, emails, and other digital
resources that point to jimm@io.com are going to be pointing nowhere in a
month. It feels like my online identity is being stolen. Except it's not
being stolen, of course&amp;mdash;merely recalled.
&lt;/p&gt;

&lt;p&gt;A Slashdot commenter also pointed out the potential security risk:
"Think about it, among other things whoever owns that domain now will
be able to intercept all mail to io.com accounts, and with the
quickness and suddenness of the transfer not everyone's who uses those
addresses is going to be able to completely transition off them before
the transfer happens"&lt;/p&gt;

&lt;p&gt;io.com was the ISP run by Steve Jackson Games&amp;mdash;the company raided
by the Secret Service because they were writing a role playing game called
"Hacker". SJ Games fought the Secret Service and won. I &lt;em&gt;think&lt;/em&gt; the
EFF grew out of that case.&lt;/p&gt;

&lt;p&gt;io.com was bought by &lt;a href="http://www.prismnet.com"&gt;prismnet.com&lt;/a&gt;
years ago. PrismNet changed hands a few times. The last guy who sold it to
the current owner (for $20) didn't sell the io.com domain. He kept it but
let them use it until July 1, 2011. My guess is he wants to sell it to I/O
Digital for a skillion dollars.&lt;/p&gt;

&lt;p&gt;From now on,
I'm &lt;a href="mailto:jim@jimmenard.com"&gt;jim@jimmenard.com&lt;/a&gt;.
Also &lt;a href="mailto:jim.menard@gmail.com"&gt;jim.menard@gmail.com&lt;/a&gt;,
or &lt;a href="mailto:jimm@prismnet.com"&gt;jimm@prismnet.com&lt;/a&gt;. Time to start
wrapping my head around being jim@jimmenard.com instead of jimm@io.com. Bye,
jimm@io.com.&lt;/p&gt;

&lt;p&gt;io.com is dead. Long live io.com.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.prismnet.com/html/iocomchange"&gt;http://www.prismnet.com/html/iocomchange&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://games.slashdot.org/story/11/06/01/0219211/A-Piece-of-Internet-History-Lost-IOcom-Sold-Services-To-Shut-Down"&gt;Slashdot:
    A Piece of Internet History Lost: IO.com Sold, Services To Shut Down&lt;/a&gt;&lt;li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-8990814544465902382?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/8990814544465902382/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=8990814544465902382' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8990814544465902382'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8990814544465902382'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2011/06/identity-recall.html' title='Identity Recall'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-4423261117806767762</id><published>2010-07-25T17:39:00.006-04:00</published><updated>2010-07-26T10:47:52.868-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='video'/><category scheme='http://www.blogger.com/atom/ns#' term='frameworks'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Play Framework Configuration</title><content type='html'>&lt;p&gt;I've been using the &lt;a href="http://www.playframework.org/"&gt;Play framework&lt;/a&gt; for a big project at work. It's fantastic. I almost hesitate to say it, but it's like Ruby on Rails for Java. Watch the video that's on the home page; it's a good quick introduction.&lt;/p&gt;

&lt;p&gt;The Tech Ops team at work wants control over configuration of the app. This makes sense for a few reasons. First, they're responsible for running it so they should be able to tweak things like memory settings. Second, they need to tell the app where the database lives, what the password is, etc. Tech Ops also wants the app to run as a WAR inside an app server. That's fine: Play comes with a &lt;code&gt;war&lt;/code&gt; command that bundles your app into either an exploded WAR or a WAR file.&lt;p&gt;

&lt;p&gt;The problem is, Play doesn't know how to read any configuration file other than the one that's in its &lt;code&gt;conf&lt;/code&gt; directory. At least, that's what I thought. It turns out there is an undocumented feature of Play configuration files: If any configuration key is named&lt;code&gt;@include.&lt;var&gt;foo&lt;/var&gt;&lt;/code&gt;, then the value is used as a path and that file is read, too.&lt;/p&gt;

&lt;p&gt;A few caveats.&lt;/p&gt;

&lt;ul&gt;

  &lt;li&gt;The path is relative to the application directory within the WAR. So, for example, to find a file in Tomcat's &lt;code&gt;conf&lt;/code&gt; directory, the path will have to be something like &lt;code&gt;../../../../../conf/play.conf&lt;/code&gt;.&lt;/li&gt;

  &lt;li&gt;The file that gets included isn't treated exactly like the normal configuration file. Magic values like &lt;code&gt;${play.path}&lt;/code&gt; and &lt;code&gt;${application.path}&lt;/code&gt; are not interpreted,.&lt;/li&gt;

  &lt;li&gt;The current Play framework id (similar to Rails' environment) is ignored. However, since the include code is run after everything else is loaded, this isn't really a problem since the external configuration file you're loading is presumably for the environment you're running.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;There is also a plugin architecture, and a hook that runs after the configuration file is read. This means that, even if &lt;code&gt;@include.&lt;var&gt;foo&lt;/var&gt;&lt;/code&gt; didn't exist, you can write a plugin to do anything you want to alter the app's configuration.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Update&lt;/b&gt;: Core Play developer Guillaume Bort wrote on the email list, "This feature is not yet documented because it is not ready: as you observed it the placeholders and the framework id stuff are not handled properly." Caveat emptor.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-4423261117806767762?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/4423261117806767762/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=4423261117806767762' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/4423261117806767762'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/4423261117806767762'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2010/07/play-framework-configuration.html' title='Play Framework Configuration'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-4822179147666568830</id><published>2009-09-12T12:31:00.003-04:00</published><updated>2009-09-14T11:46:00.119-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='filesystem'/><category scheme='http://www.blogger.com/atom/ns#' term='unix'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='shell'/><title type='text'>Up, Up, and Away</title><content type='html'>&lt;p&gt;Let's say you are in a subdirectory, and there's a Makefile in a parent
directory. How do you run make? You climb back up to the parent directory and
type "make", or use "make -C parent_dir". In either case, you have to know
what the parent dir is. Here are a pair of scripts that do that for you.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;findup&lt;/code&gt; script's job is to find a file in a parent
directory. It prints the directory name if found, else it exits with status
1.&lt;/p&gt;

    &lt;style type="text/css"&gt;
    &lt;!--
      body {
        color: #000000;
        background-color: #e6e6fa;
      }
      .builtin {
        /* font-lock-builtin-face */
        color: #da70d6;
      }
      .comment {
        /* font-lock-comment-face */
        color: #b22222;
      }
      .comment-delimiter {
        /* font-lock-comment-delimiter-face */
        color: #b22222;
      }
      .keyword {
        /* font-lock-keyword-face */
        color: #7f007f;
      }
      .sh-quoted-exec {
        /* sh-quoted-exec */
        color: #ff00ff;
      }
      .string {
        /* font-lock-string-face */
        color: #bc8f8f;
      }
      .variable-name {
        /* font-lock-variable-name-face */
        color: #b8860b;
      }

      a {
        color: inherit;
        background-color: inherit;
        font: inherit;
        text-decoration: inherit;
      }
      a:hover {
        text-decoration: underline;
      }
    --&gt;
    &lt;/style&gt;
&lt;pre&gt;&lt;code&gt;
&lt;span class="comment-delimiter"&gt;#&lt;/span&gt;&lt;span class="comment"&gt;! /bin/&lt;/span&gt;&lt;span class="keyword"&gt;sh&lt;/span&gt;&lt;span class="comment"&gt;
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;#&lt;/span&gt;&lt;span class="comment"&gt;
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;usage: findup file [file...]
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;#&lt;/span&gt;&lt;span class="comment"&gt;
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;Finds any of the files in the current directory or any directory above and
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;prints the directory name. If no such directory is found (we hit the root
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;directory), exit 1. Note: will not find files in the '/' directory.
&lt;/span&gt;
&lt;span class="variable-name"&gt;start_dir&lt;/span&gt;=&lt;span class="sh-quoted-exec"&gt;`pwd`&lt;/span&gt;

&lt;span class="keyword"&gt;while&lt;/span&gt; [ &lt;span class="sh-quoted-exec"&gt;`pwd`&lt;/span&gt; != &lt;span class="string"&gt;'/'&lt;/span&gt; ] ; &lt;span class="keyword"&gt;do&lt;/span&gt;
    &lt;span class="keyword"&gt;for&lt;/span&gt; f&lt;span class="keyword"&gt; in&lt;/span&gt; $&lt;span class="variable-name"&gt;*&lt;/span&gt; ; &lt;span class="keyword"&gt;do&lt;/span&gt;
        &lt;span class="keyword"&gt;if&lt;/span&gt; [ -f $&lt;span class="variable-name"&gt;f&lt;/span&gt; ] ; &lt;span class="keyword"&gt;then&lt;/span&gt;
            &lt;span class="builtin"&gt;echo&lt;/span&gt; &lt;span class="sh-quoted-exec"&gt;`pwd`&lt;/span&gt;
            &lt;span class="keyword"&gt;exit&lt;/span&gt; 0
        &lt;span class="keyword"&gt;fi&lt;/span&gt;
    &lt;span class="keyword"&gt;done&lt;/span&gt;
    &lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;Keep swimming...keep swimming...swimming, swimming, swimming...
&lt;/span&gt;    &lt;span class="builtin"&gt;cd&lt;/span&gt; ..
&lt;span class="keyword"&gt;done&lt;/span&gt;

&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;No parent directory
&lt;/span&gt;&lt;span class="keyword"&gt;exit&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;makeup&lt;/code&gt; script uses &lt;code&gt;findup&lt;/code&gt;.&lt;/p&gt;

    &lt;style type="text/css"&gt;
    &lt;!--
      body {
        color: #000000;
        background-color: #e6e6fa;
      }
      .builtin {
        /* font-lock-builtin-face */
        color: #da70d6;
      }
      .comment {
        /* font-lock-comment-face */
        color: #b22222;
      }
      .comment-delimiter {
        /* font-lock-comment-delimiter-face */
        color: #b22222;
      }
      .keyword {
        /* font-lock-keyword-face */
        color: #7f007f;
      }
      .sh-quoted-exec {
        /* sh-quoted-exec */
        color: #ff00ff;
      }
      .string {
        /* font-lock-string-face */
        color: #bc8f8f;
      }
      .variable-name {
        /* font-lock-variable-name-face */
        color: #b8860b;
      }

      a {
        color: inherit;
        background-color: inherit;
        font: inherit;
        text-decoration: inherit;
      }
      a:hover {
        text-decoration: underline;
      }
    --&gt;
    &lt;/style&gt;
&lt;pre&gt;&lt;/code&gt;
&lt;span class="comment-delimiter"&gt;#&lt;/span&gt;&lt;span class="comment"&gt;! /bin/&lt;/span&gt;&lt;span class="keyword"&gt;sh&lt;/span&gt;&lt;span class="comment"&gt;
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;#&lt;/span&gt;&lt;span class="comment"&gt;
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;usage: makeup [args...]
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;#&lt;/span&gt;&lt;span class="comment"&gt;
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;Finds the first makefile in the current directory or any directory above and
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;then runs make, passing on any args given to this script.
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;#&lt;/span&gt;&lt;span class="comment"&gt;
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;Relies on the "findup" command, which is found in this directory.
&lt;/span&gt;
&lt;span class="variable-name"&gt;mfdir&lt;/span&gt;=&lt;span class="sh-quoted-exec"&gt;`findup makefile Makefile`&lt;/span&gt;
&lt;span class="keyword"&gt;if&lt;/span&gt; [ -z &lt;span class="string"&gt;"$mfdir"&lt;/span&gt; ] ; &lt;span class="keyword"&gt;then&lt;/span&gt;
    &lt;span class="builtin"&gt;echo&lt;/span&gt; no makefile found
    &lt;span class="keyword"&gt;exit&lt;/span&gt; 1
&lt;span class="keyword"&gt;fi&lt;/span&gt;

&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;We've found a makefile. Use the -C flag to tell make to run from the
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;directory where we found the makefile. We do this so that error messages
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;produced during the make process are relative to the current directory. I
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;think.
&lt;/span&gt;make -C $&lt;span class="variable-name"&gt;mfdir&lt;/span&gt; $&lt;span class="variable-name"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-4822179147666568830?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/4822179147666568830/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=4822179147666568830' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/4822179147666568830'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/4822179147666568830'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2009/09/up-up-and-away.html' title='Up, Up, and Away'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-2082751031538379882</id><published>2009-08-03T15:47:00.005-04:00</published><updated>2009-08-03T16:27:46.819-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='c++'/><category scheme='http://www.blogger.com/atom/ns#' term='midi'/><category scheme='http://www.blogger.com/atom/ns#' term='music'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='audio'/><title type='text'>ChucK Hack Fork</title><content type='html'>&lt;p&gt;I've created a GitHub fork of &lt;a href="http://chuck.cs.princeton.edu/"&gt;ChucK&lt;/a&gt;, the "strongly-timed, concurrent, and on-the-fly audio programming language." In &lt;a href="http://github.com/jimm/chuck"&gt;my fork&lt;/a&gt; I've started to add missing features like string methods, and hope to add many more such as raw file I/O and perhaps some form of MIDI file I/O.&lt;/p&gt;

&lt;p&gt;So far, I've added string.ch which is a getter and setter for string characters (really one-character length substrings) and string.substr. This allowed me to write a function in ChucK that converts strings like "c#4" to MIDI note numbers.&lt;/p&gt;

&lt;p&gt;It looks like ChucK's code was last updated in 2006, so I don't feel to bad about forking it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Update&lt;/em&gt;: I just read in the chuck email list archive that the team is planning to start active development again, and they will be adding file I/O. If they get it done before I do, that would be very nice.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-2082751031538379882?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/2082751031538379882/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=2082751031538379882' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2082751031538379882'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2082751031538379882'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2009/08/chuck-hack-fork.html' title='ChucK Hack Fork'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-5394093257149626324</id><published>2009-06-22T03:52:00.004-04:00</published><updated>2009-06-22T04:00:46.767-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='humor'/><title type='text'>A Weak Man Begs Forgiveness</title><content type='html'>&lt;p&gt;Dearest Colleagues,&lt;/p&gt;

&lt;p&gt;I write this electronically-delivered letter to you in the hopes that you will forgive me. A terrible fate has befallen my lovely daughter V_______, and I am afrid that I now share in her misfortune. To protect you all from similarly horrible destinies, I must eschew your company this day.&lt;/p&gt;

&lt;p&gt;It all started innocently enough, with a persistent cough. My eldest daughter would delicately hack to "clear her throat". The coughs became more frequent and urgent. She developed a fever which burned her pure, innocent forehead. As time passed, she begain to writhe in agony, delirious with dreams of God knows not what Heavenly retributions and Hellish tortures.&lt;/p&gt;

&lt;p&gt;My dearest wife E____ conveyed her to the pediatrician's office on Sunday. Her doctor (a woman, by God! Ah, well. I know not what muse guides Science, but she must hold in favor the fairer sex) diagnosed V_______ with Swine Flu. The woman practiced her Art with confidence, perscribing the most modern treatments available.&lt;/p&gt;

&lt;p&gt;We repaired to our home, powders and poultices in hand. The cure had barely taken hold of my dear daughter when I myself began to cough. At first, I denied the obvious. What strong man would not? Having fought in The War, my thoughts were not for my own safety or bodily condition but for that of my child.&lt;/p&gt;

&lt;p&gt;I tell you now: Mr. Poe never wrote of horrors so small. The tiny demons that ravage my body even now defy all description, and to burden you with reports of the agony they cause does not merit further thought.&lt;/p&gt;

&lt;p&gt;The submarine-like conditions of the Juicyorange office being the breeding grounds for animalcules it is, it is best that I remain far, far away. This is why I have decided to remain in an entirely different State of the Union. My sorrow at not joining you is only mitigated by the knowledge that you will not be exposed to the fiends that wrack my body.&lt;/p&gt;

&lt;p&gt;In the fervent hope that this missive is not rerouted to your Spam folder,&lt;/p&gt;

&lt;p&gt;With Deepest Respect,&lt;br/&gt;
James J. Menard&lt;br/&gt;
Babbage Engine Servitor&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-5394093257149626324?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/5394093257149626324/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=5394093257149626324' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/5394093257149626324'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/5394093257149626324'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2009/06/weak-man-begs-forgiveness.html' title='A Weak Man Begs Forgiveness'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-5347751971877567957</id><published>2009-04-18T09:58:00.002-04:00</published><updated>2009-04-18T10:05:59.303-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='ajax'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Using will_paginate in Rails to Submit an AJAX Form</title><content type='html'>&lt;p&gt;I'm
  using &lt;a href="http://wiki.github.com/mislav/will_paginate"&gt;will_paginate&lt;/a&gt;
  for a Rails project. It works great, but I wanted to use it for a sidebar
  that performs searches and displays the results. It's not too hard to write
  a custom link renderer that will submit the will_paginate link using Ajax,
  but my situation was a bit different: I wanted will_paginate to inject the
  page number into the search form and submit the form using Ajax.&lt;/p&gt;

&lt;p&gt;Here's what I ended up doing: writing a link renderer that calls a
  JavaScript function that injects the page number into the form and calls the
  form's &lt;code&gt;onsubmit&lt;/code&gt; function (which then submits the form using
  Ajax).&lt;/p&gt;

&lt;p&gt;The code. First the Haml, which is in a partial that gets rendered on a
  number of pages:&lt;/p&gt;

    &lt;style type="text/css"&gt;
    &lt;!--
      body {
        color: #000000;
        background-color: #ffffff;
      }
      .comment {
        /* font-lock-comment-face */
        color: #b22222;
      }
      .constant {
        /* font-lock-constant-face */
        color: #5f9ea0;
      }
      .function-name {
        /* font-lock-function-name-face */
        color: #0000ff;
      }
      .preprocessor {
        /* font-lock-preprocessor-face */
        color: #da70d6;
      }
      .string {
        /* font-lock-string-face */
        color: #bc8f8f;
      }
      .type {
        /* font-lock-type-face */
        color: #228b22;
      }

      a {
        color: inherit;
        background-color: inherit;
        font: inherit;
        text-decoration: inherit;
      }
      a:hover {
        text-decoration: underline;
      }
    --&gt;
    &lt;/style&gt;
    &lt;pre&gt;
&lt;span class="function-name"&gt;%h1&lt;/span&gt; Asset Search
&lt;span class="preprocessor"&gt;- form_remote_tag(&lt;/span&gt;&lt;span class="constant"&gt;&lt;span class="preprocessor"&gt;:html&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt; =&amp;gt; {&lt;/span&gt;&lt;span class="constant"&gt;&lt;span class="preprocessor"&gt;:id&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt; =&amp;gt; &lt;/span&gt;&lt;span class="string"&gt;&lt;span class="preprocessor"&gt;'ssform'&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt;, &lt;/span&gt;&lt;span class="constant"&gt;&lt;span class="preprocessor"&gt;:name&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt; =&amp;gt; &lt;/span&gt;&lt;span class="string"&gt;&lt;span class="preprocessor"&gt;'ssform'&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt;}, ...) do&lt;/span&gt;
  &lt;span class="function-name"&gt;%input&lt;/span&gt;&lt;span class="preprocessor"&gt;{&lt;/span&gt;&lt;span class="constant"&gt;&lt;span class="preprocessor"&gt;:type&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt; =&amp;gt; &lt;/span&gt;&lt;span class="string"&gt;&lt;span class="preprocessor"&gt;'hidden'&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt;, &lt;/span&gt;&lt;span class="constant"&gt;&lt;span class="preprocessor"&gt;:id&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt; =&amp;gt; &lt;/span&gt;&lt;span class="string"&gt;&lt;span class="preprocessor"&gt;'sspage'&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt;, &lt;/span&gt;&lt;span class="constant"&gt;&lt;span class="preprocessor"&gt;:name&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt; =&amp;gt; &lt;/span&gt;&lt;span class="string"&gt;&lt;span class="preprocessor"&gt;'sspage'&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt;}&lt;/span&gt;
  &lt;span class="comment"&gt;/ ...&lt;/span&gt;
  &lt;span class="function-name"&gt;%input&lt;/span&gt;&lt;span class="preprocessor"&gt;{&lt;/span&gt;&lt;span class="constant"&gt;&lt;span class="preprocessor"&gt;:type&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt; =&amp;gt; &lt;/span&gt;&lt;span class="string"&gt;&lt;span class="preprocessor"&gt;'submit'&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt;, &lt;/span&gt;&lt;span class="constant"&gt;&lt;span class="preprocessor"&gt;:value&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt; =&amp;gt; &lt;/span&gt;&lt;span class="string"&gt;&lt;span class="preprocessor"&gt;'Search'&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt;}&lt;/span&gt;

  &lt;span class="preprocessor"&gt;- paginated_section(sidebar_assets, &lt;/span&gt;&lt;span class="constant"&gt;&lt;span class="preprocessor"&gt;:class&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt; =&amp;gt; &lt;/span&gt;&lt;span class="string"&gt;&lt;span class="preprocessor"&gt;'rhs_pagination'&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt;, &lt;/span&gt;&lt;span class="constant"&gt;&lt;span class="preprocessor"&gt;:renderer&lt;/span&gt;&lt;/span&gt;&lt;span class="preprocessor"&gt; =&amp;gt; SidebarSearchLinkRenderer) do&lt;/span&gt;
    &lt;span class="function-name"&gt;%table&lt;/span&gt;&lt;span class="type"&gt;.list&lt;/span&gt;
    &lt;span class="comment"&gt;/ ...&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;Next, the will_paginate renderer, in the
file &lt;code&gt;app/helpers/sidebar_search_link_renderer.rb&lt;/code&gt;. The "#" is the
  href value, and the onclick attribute calls a JavaScript funtion.&lt;/p&gt;

    &lt;style type="text/css"&gt;
    &lt;!--
      body {
        color: #000000;
        background-color: #ffffff;
      }
      .constant {
        /* font-lock-constant-face */
        color: #5f9ea0;
      }
      .function-name {
        /* font-lock-function-name-face */
        color: #0000ff;
      }
      .keyword {
        /* font-lock-keyword-face */
        color: #a020f0;
      }
      .string {
        /* font-lock-string-face */
        color: #bc8f8f;
      }
      .type {
        /* font-lock-type-face */
        color: #228b22;
      }
      .variable-name {
        /* font-lock-variable-name-face */
        color: #b8860b;
      }

      a {
        color: inherit;
        background-color: inherit;
        font: inherit;
        text-decoration: inherit;
      }
      a:hover {
        text-decoration: underline;
      }
    --&gt;
    &lt;/style&gt;
&lt;pre&gt;
&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="type"&gt;SidebarSearchLinkRenderer&lt;/span&gt; &amp;lt; &lt;span class="type"&gt;WillPaginate&lt;/span&gt;::&lt;span class="type"&gt;LinkRenderer&lt;/span&gt;

  &lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;page_link&lt;/span&gt;(page, text, attributes = {})
    &lt;span class="variable-name"&gt;@template&lt;/span&gt;.link_to text, &lt;span class="string"&gt;'#'&lt;/span&gt;, attributes.merge(&lt;span class="constant"&gt;:onclick&lt;/span&gt; =&amp;gt; &lt;span class="string"&gt;"return sidebar_search(&lt;/span&gt;&lt;span class="variable-name"&gt;#{page}&lt;/span&gt;&lt;span class="string"&gt;);"&lt;/span&gt;)
  &lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;Finally, the JavaScript function which I put
in &lt;code&gt;public/javascripts/application.js&lt;/code&gt;:&lt;/p&gt;

    &lt;style type="text/css"&gt;
    &lt;!--
      body {
        color: #000000;
        background-color: #ffffff;
      }
      .constant {
        /* font-lock-constant-face */
        color: #5f9ea0;
      }
      .function-name {
        /* font-lock-function-name-face */
        color: #0000ff;
      }
      .js2-function-param {
        /* js2-function-param-face */
        color: #2e8b57;
      }
      .keyword {
        /* font-lock-keyword-face */
        color: #a020f0;
      }
      .string {
        /* font-lock-string-face */
        color: #bc8f8f;
      }

      a {
        color: inherit;
        background-color: inherit;
        font: inherit;
        text-decoration: inherit;
      }
      a:hover {
        text-decoration: underline;
      }
    --&gt;
    &lt;/style&gt;
    &lt;pre&gt;
&lt;span class="keyword"&gt;function&lt;/span&gt; &lt;span class="function-name"&gt;sidebar_search&lt;/span&gt;(&lt;span class="js2-function-param"&gt;page&lt;/span&gt;) {
  $(&lt;span class="string"&gt;'sspage'&lt;/span&gt;).value = page;
  document.forms[&lt;span class="string"&gt;'ssform'&lt;/span&gt;].onsubmit();
  &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="constant"&gt;false&lt;/span&gt;;
}
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-5347751971877567957?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/5347751971877567957/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=5347751971877567957' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/5347751971877567957'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/5347751971877567957'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2009/04/using-willpaginate-in-rails-to-submit.html' title='Using will_paginate in Rails to Submit an AJAX Form'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-936584141807684303</id><published>2009-02-25T12:42:00.007-05:00</published><updated>2009-02-25T15:41:44.607-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Time Keeps On Slipping, So Freeze It</title><content type='html'>&lt;style type="text/css"&gt;
    &lt;!--
      body {
        color: #000000;
        background-color: #ffffff;
      }
      .comment {
        /* font-lock-comment-face */
        color: #b22222;
      }
      .comment-delimiter {
        /* font-lock-comment-delimiter-face */
        color: #b22222;
      }
      .function-name {
        /* font-lock-function-name-face */
        color: #0000ff;
      }
      .keyword {
        /* font-lock-keyword-face */
        color: #a020f0;
      }
      .type {
        /* font-lock-type-face */
        color: #228b22;
      }
    --&gt;
    &lt;/style&gt;
&lt;p&gt;A friend of a friend has &lt;a href="http://yonkeltron.com/2009/02/25/trouble-testing-timestamps/"&gt;trouble testing timestamps&lt;/a&gt;. I tried replying there, but don't think my answer got through so here it is.&lt;/p&gt;

&lt;p&gt;The short answer: all time operations should not use the wall clock, but a system time object that you create. In production, the system time object returns wall clock time. In testing, you replace/mock/modify it so that it uses the time you give it. In other words, it becomes a clock that you can freeze and re-set whenever you need to.&lt;/p&gt;

&lt;p&gt;Here is an example SystemClock class in Ruby. All of your classes should use this class to get the current date and time instead of using the Time or Date classes directly.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;Keeper of the current system time. This class is sometimes overridden during
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;testing to return different times.
&lt;/span&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="type"&gt;SystemClock&lt;/span&gt;

  &lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;self.date&lt;/span&gt;
    &lt;span class="type"&gt;Date&lt;/span&gt;.today
  &lt;span class="keyword"&gt;end&lt;/span&gt;

  &lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;self.time&lt;/span&gt;
    &lt;span class="type"&gt;Time&lt;/span&gt;.new
  &lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="keyword"&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here is the mock used during testing. You can set the time using this mock object and it will not change until it is set again.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;This mock lets you set the system date and time during testing. For example,
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;to set the date to tomorrow,
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;#&lt;/span&gt;&lt;span class="comment"&gt;
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;A few examples of use:
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;#&lt;/span&gt;&lt;span class="comment"&gt;
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;#   &lt;/span&gt;&lt;span class="comment"&gt;SystemClock.date = Date.today + 1 # Time also set: to tomorrow 9:10:11 am
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;#   &lt;/span&gt;&lt;span class="comment"&gt;SystemClock.date = nil            # Go back to using system date and time
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;#   &lt;/span&gt;&lt;span class="comment"&gt;SystemClock.date = Date.civil(2006, 4, 15)
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;#   &lt;/span&gt;&lt;span class="comment"&gt;SystemClock.time = Time.local(2006, 3, 2, 8, 42, 42) # Date changed, too
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;#&lt;/span&gt;&lt;span class="comment"&gt;
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;When you set the date, the time is set to 9:10:11 am of the same day, local
&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;time. When you set the time, the date is set to the same day.
&lt;/span&gt;
require &lt;span class="string"&gt;'models/system_clock'&lt;/span&gt;

&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="type"&gt;SystemClock&lt;/span&gt;

  &lt;span class="variable-name"&gt;@mock_date&lt;/span&gt; = &lt;span class="variable-name"&gt;nil&lt;/span&gt;
  &lt;span class="variable-name"&gt;@mock_time&lt;/span&gt; = &lt;span class="variable-name"&gt;nil&lt;/span&gt;

  &lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;self.reset&lt;/span&gt;
    &lt;span class="variable-name"&gt;@mock_date&lt;/span&gt; = &lt;span class="variable-name"&gt;nil&lt;/span&gt;
    &lt;span class="variable-name"&gt;@mock_time&lt;/span&gt; = &lt;span class="variable-name"&gt;nil&lt;/span&gt;
  &lt;span class="keyword"&gt;end&lt;/span&gt;

  &lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;self.date=&lt;/span&gt;(date)
    &lt;span class="variable-name"&gt;@mock_date&lt;/span&gt; = date
    &lt;span class="variable-name"&gt;@mock_time&lt;/span&gt; = date == &lt;span class="variable-name"&gt;nil&lt;/span&gt; ? &lt;span class="variable-name"&gt;nil&lt;/span&gt; :
      &lt;span class="type"&gt;Time&lt;/span&gt;.local(date.year, date.month, date.day, 9, 10, 11)
  &lt;span class="keyword"&gt;end&lt;/span&gt;

  &lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;self.time=&lt;/span&gt;(time)
    &lt;span class="variable-name"&gt;@mock_time&lt;/span&gt; = time
    &lt;span class="variable-name"&gt;@mock_date&lt;/span&gt; = time == &lt;span class="variable-name"&gt;nil&lt;/span&gt; ? &lt;span class="variable-name"&gt;nil&lt;/span&gt; :
      &lt;span class="type"&gt;Date&lt;/span&gt;.civil(time.year, time.month, time.day)
  &lt;span class="keyword"&gt;end&lt;/span&gt;

  &lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;self.date&lt;/span&gt;
    &lt;span class="variable-name"&gt;@mock_date&lt;/span&gt; || &lt;span class="type"&gt;Date&lt;/span&gt;.today
  &lt;span class="keyword"&gt;end&lt;/span&gt;

  &lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;self.time&lt;/span&gt;
    &lt;span class="variable-name"&gt;@mock_time&lt;/span&gt; || &lt;span class="type"&gt;Time&lt;/span&gt;.new
  &lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="keyword"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So in your normal production code you get the current time by calling &lt;code&gt;SystemClock.time&lt;/code&gt; instead of &lt;code&gt;Time.now&lt;/code&gt;. In your test code, you'd do something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;Make sure to include the mock class
&lt;/span&gt;&lt;span class="type"&gt;SystemClock&lt;/span&gt;.time = &lt;span class="type"&gt;Time&lt;/span&gt;.now &lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;Or any arbitrary time
&lt;/span&gt;thing = &lt;span class="type"&gt;Thing&lt;/span&gt;.new           &lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;Uses SystemClock to set created_at attribute
&lt;/span&gt;assert_equal &lt;span class="type"&gt;SystemClock&lt;/span&gt;.time, thing.created_at
&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-936584141807684303?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/936584141807684303/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=936584141807684303' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/936584141807684303'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/936584141807684303'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2009/02/time-keeps-on-slipping-so-freeze-it.html' title='Time Keeps On Slipping, So Freeze It'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-2552452632265011920</id><published>2009-01-28T08:58:00.004-05:00</published><updated>2009-01-28T09:02:03.499-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='midi'/><title type='text'>text2midi App Uses midilib</title><content type='html'>&lt;p&gt;Gabriel G has released &lt;a href="http://text2midi.herokugarden.com"&gt;text2midi&lt;/a&gt;, a Rails app that converts&amp;mdash;wait for it&amp;mdash;text that you paste into the Web page to MIDI, and plays it for you and lets you download the MIDI.&lt;/p&gt;

&lt;p&gt;text2midi uses &lt;a href="http://midilib.rubyforge.org/"&gt;midilib&lt;/a&gt;, my Ruby MIDI manipulation library.&lt;/p&gt;

&lt;p&gt;Go, Gabriel!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-2552452632265011920?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/2552452632265011920/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=2552452632265011920' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2552452632265011920'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2552452632265011920'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2009/01/text2midi-app-uses-midilib.html' title='text2midi App Uses midilib'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-2351217802152188419</id><published>2009-01-15T08:26:00.005-05:00</published><updated>2009-01-15T12:49:56.890-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='10gen'/><category scheme='http://www.blogger.com/atom/ns#' term='mongo'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>10gen Shifting Emphasis to the Database</title><content type='html'>&lt;p&gt;&lt;a href="http://www.10gen.com"&gt;10gen&lt;/a&gt; has announced that it is &lt;a href="http://www.10gen.com/blog/2009/1/shifting-emphasis-to-the-database"&gt;Shifting Emphasis to the Database&lt;/a&gt;. They decided to focus on the &lt;a href="http://www.mongodb.org/"&gt;Mongo database&lt;/a&gt; after finding that there was much more interest in it than in their Babble cloud computing app server.&lt;/p&gt;

&lt;p&gt;Babble is still alive, though. It has a &lt;a href="http://www.babbleapp.org/"&gt;new web site&lt;/a&gt; and has shifted to the Apache license.&lt;/p&gt;

&lt;p&gt;This may all be good news for 10gen, but it's bad news for me. Since I was working on bringing Ruby and Rails to Babble, I and a number of other fine engineers at 10gen have been let go. It hurts, but I've had a blast and learned a lot at 10gen.&lt;/p&gt;

&lt;p&gt;For the next few weeks, I'm going to be finishing up work on the Ruby Mongo driver, MongoRecord (an ActiveRecord-like framework for Mongo that is independent of Rails), and Rails ActiveRecord support for Mongo.&lt;/p&gt;

&lt;p&gt;Oh, yeah: here's &lt;a href="http://www.io.com/~jimm/Jim_Menard_resume.html"&gt;my resume&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/699020937694156193-2351217802152188419?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/2351217802152188419/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=2351217802152188419' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2351217802152188419'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2351217802152188419'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2009/01/10gen-shifting-emphasis-to-database.html' title='10gen Shifting Emphasis to the Database'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-8764019068168548789</id><published>2008-12-26T11:24:00.003-05:00</published><updated>2009-01-15T08:37:54.140-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kids'/><category scheme='http://www.blogger.com/atom/ns#' term='games'/><category scheme='http://www.blogger.com/atom/ns#' term='humor'/><title type='text'>Doing Homework With a Wii</title><content type='html'>&lt;p&gt;My younger daughter just said to me, "Dad, I know how to cheat at homework with the Wii." I asked what whe meant. She said that if you had to alphabetize a list of words (which she sometimes does for homework), you could create a Mii (an in-game avatar on the Wii) for each word, and give it that word as the name. Then use the game's "sort by name" feature to display all the Miis in alphabetical order. Voila!&lt;/p&gt;

&lt;p&gt;I'm so proud of her.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-8764019068168548789?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/8764019068168548789/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=8764019068168548789' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8764019068168548789'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8764019068168548789'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2008/12/doing-homework-with-wii.html' title='Doing Homework With a Wii'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-6609790399664470653</id><published>2008-12-23T11:35:00.004-05:00</published><updated>2008-12-23T15:28:26.079-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='10gen'/><category scheme='http://www.blogger.com/atom/ns#' term='mongo'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Rails Without ActiveRecord</title><content type='html'>&lt;p&gt;When we announced Rails support for the &lt;a href="http://www.10gen.com"&gt;10gen&lt;/a&gt; cloud computing platform, we said that ActiveRecord support was not yet included. This led naturally to the question, "what the heck good is that?"&lt;/p&gt;

&lt;p&gt;Actually, you can run Rails without ActiveRecord just fine. Rails has
been designed that way. For an example app that uses the &lt;a href="http://www.mongodb.org"&gt;Mongo&lt;/a&gt;
database, see my &lt;a href="https://github.com/jimm/10gen-rorob/tree/master"&gt;10gen-rorob&lt;/a&gt; Rails app. You might want to use the &lt;a href="https://github.com/jimm/10gen-rorob/tree/rails222"&gt;branch that uses Rails 2.2.2&lt;/a&gt; instead, because the master branch uses Rails 1.2.6 and the 2.2.2 branch also has a few more features.&lt;/p&gt;

&lt;p&gt;On the other hand, internally we have a subset of ActiveRecord working
already and are working on supporting as much of it as makes sense
with a non-relational database. I already have the Rails app from
"Agile Web Development With Rails" running unchanged.&lt;/p&gt;

&lt;p&gt;On the gripping hand, you can run the 10gen cloud yourself, in which
case you can run it with MySQL or any other relational database. In
that case, you can use ActiveRecord unchanged.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-6609790399664470653?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/6609790399664470653/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=6609790399664470653' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/6609790399664470653'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/6609790399664470653'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2008/12/rails-without-activerecord.html' title='Rails Without ActiveRecord'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-2717151745302686778</id><published>2008-12-23T11:29:00.004-05:00</published><updated>2009-02-25T15:42:32.067-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>How To Freeze Gems In Rails</title><content type='html'>&lt;p&gt;Here's how to freeze any gem into your Rails app. This is a simplified version of the larger discussion &lt;a href="http://errtheblog.com/posts/50-vendor-everything"&gt;Vendor Everything&lt;/a&gt; at err.the_blog.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copy the gems you want into the directory vendor/gems.
&lt;pre&gt;
$ mkdir vendor/gems
$ cd vendor/gems
$ gem unpack some_gem_name # repeat for each gem
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Edit config/environment.rb and add the following inside the &lt;code&gt;Rails::Initializer.run&lt;/code&gt; block:
&lt;pre&gt;
Rails::Initializer.run do |config|
  # add the next three lines
  config.load_paths += Dir["#{RAILS_ROOT}/vendor/gems/**"].map do |dir|
    File.directory?(lib = "#{dir}/lib") ? lib : dir
 end
end
&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now you can require those gems in your code, even if the system you are running on doesn't have that gem installed.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-2717151745302686778?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/2717151745302686778/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=2717151745302686778' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2717151745302686778'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2717151745302686778'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2008/12/freezing-gem-in-any-rails-app.html' title='How To Freeze Gems In Rails'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-226624250865985988</id><published>2008-12-17T23:00:00.000-05:00</published><updated>2008-12-17T23:00:20.274-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='10gen'/><category scheme='http://www.blogger.com/atom/ns#' term='mongo'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>10gen Officially Includes Ruby and Rails</title><content type='html'>&lt;p&gt;For the last four months I've been sequestered deep in the heart of the &lt;a href="http://www.10gen.com/"&gt;10gen&lt;/a&gt; labs among the test tubes full of bubbling primary-colored liquids, glass jars filled with mysterious biological samples, and mysteriously glowing rectangular pixilated displays. The whole time, I've been pressing small plastic keys in arcane sequences and muttering to myself.&lt;/p&gt;

&lt;p&gt;Finally&amp;mdash;after going through three lab coats, two lab assistants, and more brains than I care to remember&amp;mdash;the lab doors are creaking open to reveal the secrets within: Ruby and Rails are now &lt;a href="http://www.10gen.com/blog/2008/12/ruby-support-on-10gen"&gt;officially&lt;/a&gt; part of the 10gen cloud computing platform.&lt;/p&gt;

&lt;p&gt;It's alive! ... &lt;em&gt;ahem&lt;/em&gt;...  It's alive! ... &lt;em&gt;AHEM&lt;/em&gt; ... &lt;strong&gt;IT'S ALIIIIVVVVEEEE!&lt;/strong&gt;

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

&lt;p&gt;Damn. There's never a good dramatic thunderclap around when you need one.&lt;/p&gt;

&lt;p&gt;Actually, this announcement isn't worth all the theatrics. Initial Ruby and Rails support has been in the SDK for a while now. This is more of a soft launch. Ruby support should be complete. Rails support does not yet include ActiveRecord. We do have an XGen::Mongo::Base class which is Mongo-specific with an awfully ActiveRecord-like API.&lt;/p&gt;

&lt;p&gt;This launch is more of an official milestone where we are asking the community to take a look at what we've done and give us feedback. Please go grab the &lt;a href="http://www.10gen.com/wiki/SDK"&gt;10gen SDK&lt;/a&gt; and give it a try. Let me know if you have any questions or suggestions, either here or preferably on the &lt;a href="http://groups.google.com/group/10gen"&gt;10gen mailing list&lt;/a&gt;. If you are in the NYC area, come to one of our &lt;a href="http://www.10gen.com/wiki/Contact.events"&gt;hackathons&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="http://www.10gen.com/wiki/dev.Ruby"&gt;Ruby Language Center&lt;/a&gt; page for links to documentation.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-226624250865985988?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/226624250865985988/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=226624250865985988' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/226624250865985988'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/226624250865985988'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2008/12/10gen-officially-includes-ruby-and.html' title='10gen Officially Includes Ruby and Rails'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-7573475948219049708</id><published>2008-12-15T17:01:00.003-05:00</published><updated>2008-12-15T17:07:59.232-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='10gen'/><category scheme='http://www.blogger.com/atom/ns#' term='mongo'/><category scheme='http://www.blogger.com/atom/ns#' term='clojure'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Clojure Mystery Solved</title><content type='html'>&lt;p&gt;I figured out &lt;a href="http://jimmenard.blogspot.com/2008/12/clojure.html"&gt;what was wrong&lt;/a&gt; with the Clojure code in the &lt;a href="http://github.com/geir/mongo-java-driver/tree/master"&gt;Mongo Java Driver&lt;/a&gt; Clojure sample: we weren't turning the value returned by the database into a seq. You can see the fixed version &lt;a href="http://github.com/geir/mongo-java-driver/tree/master/src/examples/clojure/mongo.clj"&gt;here.&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-7573475948219049708?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/7573475948219049708/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=7573475948219049708' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/7573475948219049708'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/7573475948219049708'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2008/12/clojure-mystery-solved.html' title='Clojure Mystery Solved'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-2488405087651837557</id><published>2008-12-09T15:41:00.003-05:00</published><updated>2008-12-09T15:49:58.155-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud'/><category scheme='http://www.blogger.com/atom/ns#' term='10gen'/><category scheme='http://www.blogger.com/atom/ns#' term='mongo'/><category scheme='http://www.blogger.com/atom/ns#' term='clojure'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Ruby Mongo Driver Progress</title><content type='html'>&lt;p&gt;Over the last few days I've added a few features to the &lt;a href="http://github.com/jimm/mongo-ruby-driver/tree/master"&gt;Ruby Mongo driver&lt;/a&gt;:
&lt;ul&gt;
&lt;li&gt;Sorting
&lt;li&gt;Read/write support for arrays and regular expressions&lt;/li&gt;
&lt;li&gt;An ObjectID class and read/write support&lt;/li&gt;
&lt;li&gt;More tests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Database commands are JSON objects (sort of) which are &lt;em&gt;almost&lt;/em&gt; like Ruby hashes, but not quite. Their keys are ordered, so I whipped up an OrderedHash class to use when the driver sends commands to Mongo.&lt;/p&gt;
&lt;p&gt;Adrian Madrid (&lt;a href="mailto:aemadrid@gmail.com"&gt;aemadrid@gmail.com&lt;/a&gt;) has been helping out a lot by providing invaluable feedback, tests, suggestions, and fixes.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-2488405087651837557?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/2488405087651837557/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=2488405087651837557' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2488405087651837557'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2488405087651837557'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2008/12/ruby-mongo-driver-progress.html' title='Ruby Mongo Driver Progress'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-8264124885689608295</id><published>2008-12-07T14:10:00.004-05:00</published><updated>2008-12-08T12:33:12.998-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud'/><category scheme='http://www.blogger.com/atom/ns#' term='10gen'/><category scheme='http://www.blogger.com/atom/ns#' term='mongo'/><category scheme='http://www.blogger.com/atom/ns#' term='clojure'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Clojure</title><content type='html'>&lt;p&gt;I've been playing a bit with &lt;a href="http://clojure.org/"&gt;Clojure&lt;/a&gt; recently. Playing only: I haven't written anything serious yet. One thing I did write, though, was the &lt;a href="http://github.com/geir/mongo-java-driver/tree/master/src/examples/clojure/mongo.clj"&gt;Clojure example&lt;/a&gt; in Geir Magnusson Jr's &lt;a href="http://github.com/geir/mongo-java-driver/tree/master"&gt;Java Mongo driver&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There's a bug somewhere, though: the sample only prints records 1 and 3 out of 3. The Java sample works correctly, as does the JRuby sample. I can't figure out why, because the Clojure code that iterates over the records that are inserted is pretty simple. Here's the code:&lt;/p&gt;

&lt;pre&gt;
(def mongo (org.mongodb.driver.impl.Mongo.))
(def db (.getDB mongo "clojure"))
(def coll (.getCollection db "test"))

(. coll clear)                          ; erase all records in the collection

; insert three records
(dorun (map #(do (.insert coll {"a" (+ % 1)})) (range 0 3)))

; print the number of records in the collection.
(println "There are" (.getCount coll (org.mongodb.driver.MongoSelector.))
  "records in the collection 'test'")

; one way to do a query
(loop [i (.find coll)]
  (when i
    (do (println (first i))
        (recur (rest i)))))

; and another
(dorun (map println (.find coll)))
&lt;/pre&gt;

&lt;p&gt;If you can see what's wrong, please let me know.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-8264124885689608295?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/8264124885689608295/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=8264124885689608295' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8264124885689608295'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8264124885689608295'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2008/12/clojure.html' title='Clojure'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-5717249889115763632</id><published>2008-12-07T13:56:00.003-05:00</published><updated>2008-12-08T12:32:08.336-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud'/><category scheme='http://www.blogger.com/atom/ns#' term='10gen'/><category scheme='http://www.blogger.com/atom/ns#' term='mongo'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Ruby Mongo driver</title><content type='html'>&lt;p&gt;I've been working on a &lt;a href="http://github.com/jimm/mongo-ruby-driver/tree/master"&gt;pure-Ruby driver&lt;/a&gt; for &lt;a href="http://www.mongodb.org/"&gt;Mongo&lt;/a&gt;, 10gen's document database. If you are of the Java persuasion, then check out Geir Magnusson Jr's &lt;a href="http://github.com/geir/mongo-java-driver/tree/master"&gt;Java driver&lt;/a&gt; for Mongo.&lt;/p&gt;

&lt;p&gt;One thing that we are discussing internally at 10gen is which layer of the system should be responsible for automatically creating primary key fields for records. Mongo does not require them, but Babble (the 10gen cloud computing app server) does. I think what will end up happening is that the driver will not add them unless the caller specifically asks for it.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-5717249889115763632?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/5717249889115763632/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=5717249889115763632' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/5717249889115763632'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/5717249889115763632'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2008/12/ruby-mongo-driver.html' title='Ruby Mongo driver'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-8314071813210564271</id><published>2008-12-07T13:53:00.004-05:00</published><updated>2008-12-08T12:32:30.005-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='video'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud'/><category scheme='http://www.blogger.com/atom/ns#' term='10gen'/><title type='text'>Ruby in the Clouds Video</title><content type='html'>The videos of most of the RubyConf 2008 talks are now available online at the &lt;a href="http://rubyconf2008.confreaks.com/"&gt;Confreaks RubyConf 2008&lt;/a&gt; web site, including my talk &lt;a href="http://rubyconf2008.confreaks.com/ruby-in-the-clouds.html"&gt;Ruby in the Clouds&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-8314071813210564271?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/8314071813210564271/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=8314071813210564271' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8314071813210564271'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8314071813210564271'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2008/12/ruby-in-clouds-video.html' title='Ruby in the Clouds Video'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-5900266121499053750</id><published>2008-09-15T12:05:00.003-04:00</published><updated>2008-12-08T12:33:48.263-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud'/><category scheme='http://www.blogger.com/atom/ns#' term='10gen'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Ruby In the Cloud</title><content type='html'>&lt;p&gt;I'll be giving the talk "Ruby In the Cloud" at RubyConf 2008 in November. For a poorly-formatted outline, see &lt;a href="http://rubyconf.org/talks/20"&gt;http://rubyconf.org/talks/20&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Geir Magnusson Jr and I gave a preliminary version of this talk last week to the &lt;a href="http://nycruby.org/wiki/"&gt;nycruby&lt;/a&gt;, the NYC Ruby Users' Group. Thanks to everyone who attended for all their questions and suggestions.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-5900266121499053750?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/5900266121499053750/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=5900266121499053750' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/5900266121499053750'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/5900266121499053750'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2008/09/ruby-in-cloud.html' title='Ruby In the Cloud'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-6877912976207593341</id><published>2008-07-21T12:15:00.008-04:00</published><updated>2008-07-22T15:02:00.838-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Downpour</title><content type='html'>&lt;p&gt;Here's a bunch of big news from cloud computing application framework company &lt;a href="http://www.10gen.com/"&gt;10gen&lt;/a&gt;: their SDK is now &lt;a href="http://www.10gen.com/blog/2008/7/10gen-platform-sdk-available"&gt;available for download&lt;/a&gt;, they are making the entire platform &lt;a href="http://www.10gen.com/blog/2008/7/10gen-platform-is-open-source"&gt;Open Source&lt;/a&gt;, and they've received &lt;a href="http://www.10gen.com/blog/2008/7/financing"&gt;Series A financing&lt;/a&gt;. Congratulations to 10gen for reaching these big milestones, and for the decision to make their code Open Source.&lt;/p&gt;

&lt;p&gt;In related news, I'm joining 10gen as of August 11th. I can't wait to start. I'm hoping to be able to blog about what I'll be working on: improving their Ruby implementation and making Ruby on Rails work on their cloud computing platform with Mongo, the 10gen distributed OO database.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-6877912976207593341?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/6877912976207593341/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=6877912976207593341' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/6877912976207593341'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/6877912976207593341'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2008/07/downpour.html' title='Downpour'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-3360814095066190562</id><published>2008-01-07T09:38:00.000-05:00</published><updated>2008-01-07T09:44:31.160-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Magpies and Shiny Things</title><content type='html'>&lt;p&gt;Over at &lt;a href="http://www.codinghorror.com/blog/"&gt;Coding Horror&lt;/a&gt; Jeff Atwood just posted &lt;a href="http://www.codinghorror.com/blog/archives/000916.html"&gt;The Magpie Developer&lt;/a&gt;. As somebody with a blog named "Shiny Things", I'm well aware of my penchant for new technologies. Recently, it's been &lt;a href="http://www.scala-lang.org/"&gt;Scala&lt;/a&gt; and &lt;a href="http://erlang.org/"&gt;Erlang&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The wise magpie doesn't throw away his old shiny things. He takes what he learns from new technologies and tries to apply them to what he's using now.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-3360814095066190562?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/3360814095066190562/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=3360814095066190562' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/3360814095066190562'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/3360814095066190562'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2008/01/over-at-coding-horror-jeff-atwood-just.html' title='Magpies and Shiny Things'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-2309857642762115349</id><published>2007-11-16T08:46:00.002-05:00</published><updated>2008-03-24T14:37:03.368-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='music'/><title type='text'>LilyPond</title><content type='html'>&lt;p&gt;I've always wanted to learn to use &lt;a href="http://lilypond.org/"&gt;LilyPond&lt;/a&gt;. My first two attempts are &lt;a href="http://www.io.com/~jimm/music/mosquito_bite_rag.pdf"&gt;Mosquito Byte Rag&lt;/a&gt; [link fixed] (PDF, 148K) and a piano version of the &lt;a href="http://www.io.com/~jimm/music/main_theme_piano.pdf"&gt;Equal Rites Main Theme&lt;/a&gt; (PDF, 117K). There are no dynamic or note markings yet.&lt;/p&gt;

&lt;p&gt;I wrote Mosquito Bite Rag thirty years ago. &lt;em&gt;Thirty. Years. Ago.&lt;/em&gt; That makes me feel old, but what's worse is that I haven't written anything seriously in over ten years. Even the Equal Rites theme is around fifteen years old.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-2309857642762115349?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/2309857642762115349/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=2309857642762115349' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2309857642762115349'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2309857642762115349'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/11/lilypond.html' title='LilyPond'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-1933355390813108128</id><published>2007-11-16T08:41:00.001-05:00</published><updated>2007-11-16T08:46:48.205-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='firefox'/><category scheme='http://www.blogger.com/atom/ns#' term='osx'/><title type='text'>JavaScript Crashing Mac OS X Firefox 2.0.0.9</title><content type='html'>&lt;p&gt;Recently I upgraded to Firefox 2.0.0.9 on Mac OS X. Ever since then, Firefox has been crashing extremely frequently. Most often, it's when I'm closing a tab---usually GMail.&lt;/p&gt;

&lt;p&gt;When the Apple problem reporter app fires up, I get to see the stack. Every time I've checked, the crash has been in the JavaScript engine somewhere (the times I've remembered to check, it's always crashed in libmozjs.dylib).&lt;/p&gt;

&lt;p&gt;I just reproduced the problem frighteningly easily: after a crash I restarted Firefox. I then went to GMail. After the page loaded, I closed the single tab containing GMail. Crash. Ouch.&lt;/p&gt;

&lt;p&gt;What is keeping me from switching to Safari or Opera? At this point, familiarity and Google's bookmark synchronizer. The synchronizer has its own problems, though.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-1933355390813108128?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/1933355390813108128/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=1933355390813108128' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/1933355390813108128'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/1933355390813108128'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/11/javascript-crashing-mac-os-x-firefox.html' title='JavaScript Crashing Mac OS X Firefox 2.0.0.9'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-8714407780468146832</id><published>2007-11-02T08:37:00.001-04:00</published><updated>2008-12-09T16:37:20.422-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='email'/><category scheme='http://www.blogger.com/atom/ns#' term='google'/><title type='text'>Gmail Adds "Delete" Keyboard Shortcut</title><content type='html'>&lt;p&gt;Today my GMail account was upgraded to the new interface. The contacts management interface has been improved, but that's not what I'm excited about.&lt;/p&gt;

&lt;p&gt;On a whim, I decided to review the keyboard shortcuts found at http://mail.google.com/support/bin/answer.py?answer=6594. Guess what? Google has added "#" as a keyboard shortcut for "delete". YAY! They've also added a bunch of other keyboard shortcuts I haven't seen before: mark as (un)read, archive and previous/next, and undo.&lt;/p&gt;

&lt;p&gt;As a die-hard keyboarder, the lack of a "delete" shortcut has been most annoying. I've been waiting for this ever since GMail launched.&lt;/p&gt;

&lt;p&gt;Thanks, Google.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-8714407780468146832?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/8714407780468146832/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=8714407780468146832' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8714407780468146832'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8714407780468146832'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/11/gmail-adds-delete-keyboard-shortcut.html' title='Gmail Adds &quot;Delete&quot; Keyboard Shortcut'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-7381971056873555468</id><published>2007-10-08T14:53:00.001-04:00</published><updated>2007-10-08T14:54:01.800-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='emacs'/><title type='text'>Halloween Emacs Tips</title><content type='html'>&lt;p&gt;Here in time for Halloween: a new section on my Emacs tips page about &lt;a href="http://www.io.com/~jimm/emacs_tips.html#skeletons"&gt;skeletons&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/699020937694156193-7381971056873555468?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/7381971056873555468/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=7381971056873555468' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/7381971056873555468'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/7381971056873555468'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/10/halloween-emacs-tips.html' title='Halloween Emacs Tips'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-2625504801951877372</id><published>2007-08-07T10:27:00.001-04:00</published><updated>2007-08-07T10:27:12.794-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Yaws and Mnesia</title><content type='html'>&lt;p&gt;I'm playing with translating a small Web application from &lt;a href="http://www.rubyonrails.org/"&gt;Rails&lt;/a&gt; to the Erlang web app framework &lt;a href="http://yaws.hyber.org/"&gt;Yaws&lt;/a&gt;. When I tried to access a Mnesia database from a page, it failed because Mnesia wasn't running. Oops. I tried running it from another erl shell, but that didn't work.&lt;/p&gt;

&lt;p&gt;After asking on &lt;a href="http://www.erlang.org/mailman/listinfo/erlang-questions"&gt;erlang-questions&lt;/a&gt;, here's what I've learned from the responses: You can tell Yaws about Mnesia by using the command line switch '--mnesiadir full-path-to-mnesia-directory' (but that only works when yaws is running as a daemon).&lt;/p&gt;

&lt;p&gt;Or, you can connect to the yaws (erl) runtime and start Mnesia from there. If you started Yaws with -sname and cookie you can connect to it like any other Erlang runtime.&lt;/p&gt;

&lt;p&gt;Finally, the simplest way to do this is to run yaws from the command
line and start mnesia from within the yaws session. That's what I've done.&lt;/p&gt;

&lt;p&gt;Thanks to the members of erlang-questions for their help.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-2625504801951877372?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/2625504801951877372/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=2625504801951877372' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2625504801951877372'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2625504801951877372'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/08/yaws-and-mnesia.html' title='Yaws and Mnesia'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-8571058640052172737</id><published>2007-07-18T09:31:00.001-04:00</published><updated>2007-07-18T09:31:37.065-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='humor'/><title type='text'>Erlang Notes</title><content type='html'>&lt;p&gt;Two &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt;-related notes, neither about the language &lt;i&gt;per se&lt;/i&gt;. First, take a look at &lt;a href="http://www.clickcaster.com/channel/item/why-do-you-like-erlang"&gt;"Why do you like Erlang?"&lt;/a&gt;. I love the Magic card. The article's good, too.&lt;/p&gt;

&lt;p&gt;Second, my Erlang blog entries for June were selected as that month's contest winners by Pay Eyler of &lt;a href="http://on-erlang.blogspot.com/"&gt;On Erlang&lt;/a&gt;, &lt;a href="http://pragdave.pragprog.com/"&gt;PragDave Thomas&lt;/a&gt;, and &lt;a href="http://armstrongonsoftware.blogspot.com/"&gt;Joe Armstrong&lt;/a&gt;. Thanks to all of them for the honor. There has been no official announcement (and there might not be), but my copy of &lt;a href="http://pragmaticprogrammer.com/titles/jaerlang/index.html"&gt;Programming Erlang&lt;/a&gt; is on its way now that the paper edition is shipping. I've had my PDF version for quite a while, and I highly recommend the book.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-8571058640052172737?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/8571058640052172737/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=8571058640052172737' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8571058640052172737'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8571058640052172737'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/07/erlang-notes.html' title='Erlang Notes'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-1230104256031817091</id><published>2007-06-28T08:33:00.001-04:00</published><updated>2007-06-28T08:34:28.486-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='filesystem'/><category scheme='http://www.blogger.com/atom/ns#' term='humor'/><title type='text'>CSI:Munich - ZFS Saves the World</title><content type='html'>&lt;p&gt;&lt;a href="http://video.google.com/videoplay?docid=8100808442979626078"&gt;CSI:Munich - ZFS Saves the World&lt;/a&gt;. Warning: for geeks only.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-1230104256031817091?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/1230104256031817091/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=1230104256031817091' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/1230104256031817091'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/1230104256031817091'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/06/csimunich-zfs-saves-world.html' title='CSI:Munich - ZFS Saves the World'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-2478696430513056963</id><published>2007-06-27T09:03:00.001-04:00</published><updated>2007-06-27T09:03:37.281-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><category scheme='http://www.blogger.com/atom/ns#' term='humor'/><title type='text'>Star Ship Dave</title><content type='html'>&lt;p&gt;Occasionally movies, TV shows, and ads are filmed around where I work in NYC. I've never seen a "star", but I often see the food trailers and office trailers and once or twice I've seen locations with cameras and lights&amp;mdash;no action yet, though.&lt;/p&gt;

&lt;p&gt;For a few weeks last fall, an extras tent was set up on the street where I work. The costumes on the extras were really cool: modern, colorful versions of '30s suits and dresses.&lt;/p&gt;

&lt;p&gt;Today I saw a catering setup. Nothing odd about that. What was odd was the sign hand-written on a whiteboard attached to the food truck: "Welcome, Star Ship Dave". I can't help chuckling every time I think of that title. Whether or not it's a working title, it's got me wondering. Who is Dave? Is it the ship?&lt;/p&gt;

&lt;p&gt;Perhaps it's a Dave we know already: David Hasselhoff, Dave Chapelle, David Letterman, Dave Barry, or David Bowie. (Nah, not Bowie&amp;mdash;some Davids are always "David", never "Dave".)&lt;/p&gt;

&lt;p&gt;Perhaps I'll brave the 90-degree heat today and venture outside in a while. I want to see what the costumes look like.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-2478696430513056963?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/2478696430513056963/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=2478696430513056963' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2478696430513056963'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2478696430513056963'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/06/star-ship-dave.html' title='Star Ship Dave'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-8717818637166445154</id><published>2007-06-23T11:01:00.003-04:00</published><updated>2007-06-23T11:01:23.899-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Erlang Boids Simulation Design</title><content type='html'>&lt;p&gt;As one of my &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt; programming exercises, I decided to write the non-graphical part of a &lt;a href="http://www.red3d.com/cwr/boids/"&gt;Boids&lt;/a&gt;&lt;/a&gt; simulation. I've written the same thing in a few different languages (Java, Ruby, Lisp, C++) before. Using Erlang would be interesting because of its support for message-based parallelism and concurrency.&lt;/p&gt;

&lt;p&gt;I figured at first that each bird should run in its own process. In each of my other implementations there is also a flock object. Not necessarily a big-O Object, but at least some central place where the birds are collected that can answer questions about them such as the average velocity of the flock and its center of mass. Even with a multi-process Erlang implementation, it makes sense to have a flock process. Flocking behavior rules depend upon each bird's knowledge of the position and velocity of all other birds in the flock (more realistically, all other birds it can see). There are two choices: make each bird know about all other birds or have a flock object that can answer questions about all birds. Making each boid (bird) aware of all the others seems to be a bad design decision: it requires O(n&lt;sup&gt;2&lt;/sup&gt;) communications overhead for every calculation. Each boid has to ask all the others its position and velocity all the time.&lt;/p&gt;

&lt;p&gt;With a flock object, the same communications are O(n) because the flock can ask each boid for its position and velocity, and then respond with the aggregate information (average positions, average velocities) when asked.The number of calculations is still O(n&lt;sup&gt;2&lt;/sup&gt;) because each boid wants to know the average position and velocity excluding itself. It's the number of messages and the number of connections between processes that is O(n). To reduce the number of calculations to O(n), you could ignore the "excluding itself" qualification.&lt;p&gt;

&lt;p&gt;My first Erlang implementation resulted in a nasty deadlock. The flock periodically messaged each boid, telling it to move. When the boid received the move message, it updated its position and velocity. In order to do that, it had to message the flock, asking for information about the other boids. When that happened, the program hung because the flock not only wasn't listening for messages, it was still waiting for a response from that boid. Deadlock!&lt;p&gt;

&lt;p&gt;The next version had the flock asking each boid for its location information first, and then telling each boid to move, pasing in the information it needed. This worked, but it was synchronous and definitely not elegant.&lt;/p&gt;

&lt;p&gt;Finally, I hit upon a better design: The flock would request position information from each boid periodically, and separately each boid would peroidically tell itself to move, asking the flock for the other boid's information when it needed it. There is a chance that a boid could have moved while other boids were relying upon it's old position and velocity cached by the flock, but that's not really important. In real life birds using old, slightly out-of-date information all the time: they blink, or are distracted by a bug, or are subject to the speed of light's limitations on information gathering.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-8717818637166445154?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/8717818637166445154/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=8717818637166445154' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8717818637166445154'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8717818637166445154'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/06/erlang-boids-simulation-design.html' title='Erlang Boids Simulation Design'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-2933826563590369134</id><published>2007-06-23T11:01:00.001-04:00</published><updated>2007-06-23T11:05:17.879-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='firefox'/><category scheme='http://www.blogger.com/atom/ns#' term='edit'/><category scheme='http://www.blogger.com/atom/ns#' term='text'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Firefox: Of Thee I Sing, Thunderbird: Of Thee I Hum</title><content type='html'>&lt;p&gt;&lt;strong&gt;or: Don't Fence Me In&lt;br/&gt;
or: Freedom, by George Michaels&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've tried the new &lt;a href="http://www.apple.com/safari/"&gt;Safari Beta 3&lt;/a&gt;. It's really fast and refreshingly standards-compliant. I don't care for the brushed aluminum look, though supposedly that's going away.&lt;/p&gt;

&lt;p&gt;There's no way I'll switch from &lt;a href="http://www.mozilla.com/firefox/"&gt;Firefox&lt;/a&gt;, though. There are two main reasons: I rely on many useful Firefox extensions, and Firefox is still more cross-platform than Safari. Firefox also supports more search engines in its search text field. To be fair and balanced, I must point out that Firefox 2.X crashes or hangs on Mac OS X all the time. I put up with that because of all the other benefits.&lt;/p&gt;

&lt;p&gt;That first reason is the biggest though. Firefox is more hackable, which makes it more useful. Here is the list of Firefox extensions I use. The &lt;a href="https://addons.mozilla.org/firefox/"&gt;Firefox Add-ons&lt;/a&gt; page is also a great resource.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.google.com/tools/firefox/browsersync/index.html"&gt;Google Browser Synch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://adblock.mozdev.org/"&gt;Adblock&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.getfirebug.com/"&gt;Firebug&lt;/a&gt; for Web development&lt;/li&gt;
&lt;li&gt;&lt;a href="http://chrispederick.com/work/web-developer/"&gt;Web Developer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://livehttpheaders.mozdev.org/"&gt;LiveHttpHeaders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://home.etu.unige.ch/~robin0/LongTitles_en.html"&gt;Long Titles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://del.icio.us/help/firefox/extension"&gt;del.icio.us&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://addons.mozilla.org/firefox/4125/"&gt;It's All Text!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://dafizilla.sourceforge.net/viewsourcewith/"&gt;ViewSourceWith&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/p/nagioschecker/"&gt;Nagios Checker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://conkeror.mozdev.org/"&gt;Conkeror&lt;/a&gt; Emacs key bindings. FF 1.5+&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"It's All Text!" lets you edit any textarea's text using your favorite editor. Mine's set to use Emacs, of course.  You can set the editor in the preference dialog box, but it might be easier for you to edit the Firefox's user prefs Javascript file and add&lt;/p&gt;

&lt;pre&gt;
user_pref("extensions.itsalltext.editor",
          "/Applications/Emacs.app/Contents/MacOS/Emacs");
&lt;/pre&gt;

&lt;p&gt;I should probably use emacsclient instead so the file is open in the currently running Emacs instead of in a new instance.&lt;/p&gt;

&lt;p&gt;Switching to another browser like Safari would force me to give up too many extensions that I have come to rely on. Google Browser Synch is probably the most useful to me, with Firebug and LiveHttpHeaders coming in a close second and third.&lt;/p&gt;

&lt;p&gt;Safari does have some features that Firefox doesn't, of course. Sharing your bookmarks with those around you via Bonjour and integration with Apple's AddressBook are the two I can think of. I don't want either. How often have I wanted to share all my bookmarks with others or see all of theirs? Never. Hell, I password-protect my Firefox so that if other people use it they won't see the form field values that Firefox saves for me.&lt;/p&gt;

&lt;p&gt;There are some little things that bug me about Safari, too. For example, the keyboard shortcut to open/close the bookmarks sidebar is Option-Command-B and the command to switch to the Google search text field is Option-Command-F. I hate the Option-Command combo; it forces me to switch my whole left hand position away from the home row. In Firefox it's Command-B and Command-K for Mac OS X, Control-B and Control-K for Windows and Linux.&lt;/p&gt;

&lt;p&gt;For similar reasons, I use Thunderbird instead of Apple's Mail. Not because of the extensions (I only use one: &lt;a href="http://enigmail.mozdev.org/"&gt;Enigmail&lt;/a&gt;), but because it is cross-platform, uses a standard format for email message storage, and uses an open format for its address book. I'm using Mac OS X now, but it's possible that I might want to switch to Linux (Ubuntu is quite nice) or be forced to switch to Windows at some point. I can use Thunderbird on those platforms, too.&lt;/p&gt;

&lt;p&gt;When I must open Word documents, Excel spreadsheets, and PowerPoint presentations I use &lt;a href="http://www.neooffice.org/neojava/en/index.php"&gt;NeoOffice&lt;/a&gt;. On Windows, I use &lt;a href="http://www.openoffice.org/"&gt;OpenOffice.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's most important to me that my data is portable not only between operating systems but also between computers and applications. I refuse to get locked down into any proprietary format or have my data locked down on only one computer. I'm letting Google store my (encrypted) browser information, but I have my own copies of the data on more than one computer, inside Firefox itself.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-2933826563590369134?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/2933826563590369134/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=2933826563590369134' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2933826563590369134'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/2933826563590369134'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/06/firefox-of-thee-i-sing-thunderbird-of.html' title='Firefox: Of Thee I Sing, Thunderbird: Of Thee I Hum'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-8896224951775672201</id><published>2007-06-09T08:29:00.000-04:00</published><updated>2007-06-09T08:30:51.115-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><category scheme='http://www.blogger.com/atom/ns#' term='humor'/><category scheme='http://www.blogger.com/atom/ns#' term='science'/><title type='text'>Feeling Left Behind</title><content type='html'>&lt;p&gt;A few months ago, a study surfaced on Reddit that stated that the ability to digest milk in human adults is a relatively recent adaptation. It further posited that this ability was convincing proof of the theory of evolution: humans have evolved to be able to digest lactose later in life.&lt;/p&gt;

&lt;p&gt;Years and years ago, I remember reading one of those fun kids' science articles that talked about genetic traits like the ability to roll your tongue or wiggle your ears. The one that stuck with me was that some people have a small, solitary hair between the last two knuckles of the ring fingers on either hand. I always imagined that having that hair proved that you were a genetic throwback.&lt;/p&gt;

&lt;p&gt;I have that hair. I'm also slightly lactose intolerant. I feel like a caveman. Maybe I could get a job acting in a Geiko commercial.&lt;/p&gt;

&lt;p&gt;At least I have some compensations: I can roll my tongue and wiggle my ears. So there, all you evolved monkeys!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-8896224951775672201?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/8896224951775672201/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=8896224951775672201' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8896224951775672201'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8896224951775672201'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/06/feeling-left-behind.html' title='Feeling Left Behind'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-4883024273085021082</id><published>2007-06-05T12:30:00.000-04:00</published><updated>2007-06-29T10:10:28.977-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Speedier Erlang</title><content type='html'>&lt;p&gt;In the comments to &lt;a href="http://jimmenard.blogspot.com/2007/06/erlang-fractal-benchmark.html"&gt;Erlang Fractal Benchmark&lt;/a&gt;, Ulf Wiger noted in the comments that adding &lt;code&gt;is_float&lt;/code&gt; guard clauses speeds up the code.&lt;/p&gt;

&lt;p&gt; When I asked why on the Erlang email list, Ulf and others explained: when the compiler sees &lt;code&gt;is_float(X)&lt;/code&gt; it knows that &lt;code&gt;X&lt;/code&gt; must be a float. Instead of worrying about type checks, casting, and other inefficiencies, it can optimize the code that uses X.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-4883024273085021082?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/4883024273085021082/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=4883024273085021082' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/4883024273085021082'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/4883024273085021082'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/06/speedier-erlang.html' title='Speedier Erlang'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-5247575653359958741</id><published>2007-06-04T08:21:00.000-04:00</published><updated>2007-06-29T10:11:23.493-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Erlang Fractal Benchmark</title><content type='html'>&lt;p&gt;While looking at a simple &lt;a href="http://www.timestretch.com/FractalBenchmark.html"&gt;fractal benchmark&lt;/a&gt; that showed up on the &lt;a href="http://programming.reddit.com"&gt;programming Reddit&lt;/a&gt;, I noticed that there wasn't an &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt; version. Below is one I wrote last night. Erlang fares rather well. One thing mildly surprised me: it runs &lt;em&gt;slightly&lt;/em&gt; faster in an Erlang shell within Emacs than in both Apple's Terminal and iTerm on Mac OS X. Within Emacs it runs in 1.09000 (runtime) 1.14100 (wall clock) seconds. In both Terminal and iTerm it runs in around  1.11000 (runtime) 1.16600 (wall clock) seconds. Perhaps screen I/O isn't as fast in the terminal programs.&lt;/p&gt;

&lt;p&gt;Two caveats: first, these numbers were generated on my 2.33 GHz Intel MacBook Pro; I don't know what the original benchmarks used. Also, I only ran the code a handful of times and picked a "typical" time to report. A better test would have been to run the code hundreds or thousands of times and average the values.&lt;/p&gt;

&lt;p&gt;This post also says a bit about intuition vs. measuring. I discuss some code modifications and their expected and actual effects below.&lt;p&gt;

&lt;p&gt;Another thing to note: the author of the fractal benchmark page says that he hasn't bothered to optimize the code for each language he tested. I don't know if using &lt;code&gt;lists:map/2&lt;/code&gt; or extracting &lt;code&gt;iter_value/5&lt;/code&gt; and using guard clauses would disqualify this version in his opinion.&lt;/p&gt;

&lt;pre&gt;
-module(fractal_benchmark).
-author("Jim Menard, jimm@io.com").
-export([run/0]).

-define(BAILOUT, 16).
-define(MAX_ITERATIONS, 1000).

%% Idea from http://www.timestretch.com/FractalBenchmark.html

run() -&gt;
    io:format("Rendering~n"),
    statistics(runtime),
    statistics(wall_clock),
    lists:map(fun(Y) -&gt;
                      io:format("~n"),
                      lists:map(fun(X) -&gt; plot(X, Y) end, lists:seq(-39, 39))
              end,
              lists:seq(-39, 39)),
    io:format("~n"),
    {_, Time1} = statistics(runtime),
    {_, Time2} = statistics(wall_clock),
    Sec1 = Time1 / 1000.0,
    Sec2 = Time2 / 1000.0,
    io:format("Erlang Elapsed ~p (runtime) ~p (wall clock) seconds~n",
              [Sec1, Sec2]).

plot(X, Y) -&gt;
    case iterate(X/40.0, Y/40.0) of
        0 -&gt;
            io:format("*");
        _ -&gt;
            io:format(" ")
    end.

iterate(X, Y) -&gt;
    CR = Y - 0.5,
    CI = X,
    iter_value(CR, CI, 0.0, 0.0, 0).

iter_value(_, _, _, _, I) when I &gt; ?MAX_ITERATIONS -&gt;
    0;
iter_value(_, _, ZI, ZR, I) when ZI * ZI + ZR * ZR &gt; ?BAILOUT -&gt;
    I;
iter_value(CR, CI, ZI, ZR, I) -&gt;
    Temp = ZR * ZI,
    ZR2 = ZR * ZR,
    ZI2 = ZI * ZI,
    ZRnew = ZR2 - ZI2 + CR,
    ZInew = Temp + Temp + CI,
    iter_value(CR, CI, ZInew, ZRnew, I + 1).
&lt;/pre&gt;

&lt;p&gt;You might have noticed that &lt;code&gt;ZI * ZI&lt;/code&gt; and &lt;code&gt;ZR * ZR&lt;/code&gt; are calculated twice: once in the body of the last clause and once in the second guard clause. The guard clause has to be executed every time, which means that running the last, most frequently executed clause executes the multiplications twice. I tried pre-calculating those values and adding them as parameters, so the arg list is&lt;/p&gt;

&lt;pre&gt;
iter_value(_CR, _CI, _ZI, _ZI2, _ZR, _ZR2, I)
&lt;/pre&gt;

&lt;p&gt;Did it help? It did indeed. Execution time in Emacs went down to 0.890000 (runtime) 0.919000 (wall clock) seconds. Here are the modified versions of &lt;code&gt;iterate&lt;/cod&gt; and &lt;code&gt;iter_value&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;
iterate(X, Y) -&gt;
    CR = Y - 0.5,
    CI = X,
    iter_value(CR, CI, 0.0, 0.0, 0.0, 0.0, 0).

iter_value(_, _, _, _, _, _, I) when I &gt; ?MAX_ITERATIONS -&gt;
    0;
iter_value(_, _, _, ZI2, _, ZR2, I) when ZI2 + ZR2 &gt; ?BAILOUT -&gt;
    I;
iter_value(CR, CI, ZI, ZI2, ZR, ZR2, I) -&gt;
    Temp = ZR * ZI,
    ZRnew = ZR2 - ZI2 + CR,
    ZInew = Temp + Temp + CI,
    iter_value(CR, CI, ZInew, ZInew * ZInew, ZRnew, ZRnew * ZRnew, I + 1).
&lt;/pre&gt;

&lt;p&gt;One final thing I tried was commenting out the calls to &lt;code&gt;io:format&lt;/code&gt;. In my experience, screen I/O usually slows things down quite a bit. (In the case statement in &lt;code&gt;plot/2&lt;/code&gt;, I had to replace them with &lt;code&gt;void&lt;/code&gt; statements instead of simply commenting them out.) The result: execution time went down to 0.830000 (runtime) 0.839000 (wall clock) seconds within Emacs. In iTerm, execution time was only slightly slower than that. So the time decreased, but not nearly as much as it did when I removed the extra multiplications. I'm surprised. Is multiplication that expensive in Erlang, or is I/O well optimized, or was my instinct wrong? Come to think of it, &lt;code&gt;io:format&lt;/code&gt; is only called once per coordinate; the multiplications happen thousands of times for each.&lt;/p&gt;

&lt;p&gt;A distributed version of this algorithm is certainly possible (say, one process for every X,Y coordinate). My gut tells me that it would run slower because the calculation for an individual coordinate is relatively small and the message passing overhead and gathering and coordination of the results would outweigh the benefits. My intuition was wrong about the effects of I/O, though. I'd have to try it to make sure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additions:&lt;/strong&gt; In the comments, Ulf Wiger suggested that I add &lt;code&gt;is_float&lt;/code&gt; guards for all the function parameters that are floats. Doing this to &lt;code&gt;iter_value/7&lt;/code&gt; reduced execution time by almost half to 0.450000 (runtime) 0.455000 (wall clock) seconds, a huge savings. (Does anybody know why adding these guard clauses speeds up execution? I would imaging that extra checking would slow it down.) Here's the new code for &lt;code&gt;iter_value/7&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;
iter_value(_CR, _, _ZI, _ZI2, _ZR, _ZR2, I)
  when I &gt; ?MAX_ITERATIONS,
  is_float(_CR), is_float(_ZI), is_float(_ZI2), is_float(_ZR), is_float(_ZR2) -&gt;
    0;
iter_value(_CR, _, _ZI, _ZI2, _ZR, _ZR2, I)
  when _ZI2 + _ZR2 &gt; ?BAILOUT,
  is_float(_CR), is_float(_ZI), is_float(_ZI2), is_float(_ZR), is_float(_ZR2) -&gt;
    I;
iter_value(CR, CI, ZI, ZI2, ZR, ZR2, I)
  when
  is_float(CR), is_float(ZI), is_float(ZI2), is_float(ZR), is_float(ZR2) -&gt;
    Temp = ZR * ZI,
    ZRnew = ZR2 - ZI2 + CR,
    ZInew = Temp + Temp + CI,
    iter_value(CR, CI, ZInew, ZInew * ZInew, ZRnew, ZRnew * ZRnew, I + 1).
&lt;/pre&gt;

&lt;p&gt;Ulf and others also suggested that I avoid printing each character
separately. Doing so did not seem to change execution time at all. Here's what I did:&lt;/p&gt;

&lt;pre&gt;
% changed the inner map call to plot
run() -&gt;
    % ...
    Seq = lists:seq(-39, 39),
    lists:map(fun(Y) -&gt;
                      CharList = lists:map(fun(X) -&gt; plot(X, Y) end, Seq),
                      io:format("~s~n", [CharList])
              end,
              Seq),
    % ...

% changed plot to return single-character strings
plot(X, Y) -&gt;
    case iterate(X/40.0, Y/40.0) of
        0 -&gt;
            "*";
        _ -&gt;
            " "
    end.
&lt;/pre&gt;

&lt;p&gt;I also tried commenting out the &lt;code&gt;io:format/2&lt;/code&gt; call above. Execution time went up about 1/100 of a second.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-5247575653359958741?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/5247575653359958741/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=5247575653359958741' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/5247575653359958741'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/5247575653359958741'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/06/erlang-fractal-benchmark.html' title='Erlang Fractal Benchmark'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-7451914363978487826</id><published>2007-05-22T22:11:00.000-04:00</published><updated>2007-05-23T17:08:24.446-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='midi'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>An Erlang MIDI File Reader/Writer</title><content type='html'>&lt;p&gt;I've been learning &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt;. (See &lt;a href="http://jimmenard.blogspot.com/2007/05/learning-new-programming-language.html"&gt;Learning a New Programming Language&lt;/a&gt;.) As part of that process, I've written three programs so far: a &lt;a href="http://en.wikipedia.org/wiki/Boids"&gt;Boids&lt;/a&gt; flock simulation, a simple Web application using Erlyweb, and a MIDI file reader/writer.&lt;/p&gt;

&lt;p&gt;One point that I might not have made clearly in my previous post is the reasons that I learn a new language: to learn new ways to think about and solve coding problems, to see what's out there that's better than what I'm using (I try not to be a &lt;a href="http://www.paulgraham.com/avg.html"&gt;Blub programmer&lt;/a&gt;), to keep my brain sharp, and because it's fun.&lt;/p&gt;

&lt;p&gt;Coding the MIDI file library helped me learn about Erlang's file I/O, the binary data type, and a bit more about pattern matching. (The Boids simulation has much more pattern matching goodness; more about that in another post.) The code isn't distributed, nor does it really take advantage of many of Erlang's strengths such as distributed process (Boids does). It does make use of the binary data type and pattern matching quite heavily.&lt;/p&gt;

&lt;p&gt;I was also forced to think about data representation in Erlang: how would I represent a sequence, a track, and a single MIDI event? I'm not convinced that I have come up with the best representations, because I have not yet had the time to use this library for any MIDI file manipulation. When I get to that point, I fully expect that the data representation will change.&lt;/p&gt;

&lt;p&gt;Having written this code in a few different languages before, I knew where to start: with the easy stuff! I first defined the constants. &lt;code&gt;midi_consts.hrl&lt;/code&gt; contains all of the define statements for all the constants I might need. It was while creating this file that I learned how to write hex numbers in Erlang. (That sounds like a small, silly thing but I was quite annoyed for a few minutes before I figure out how to do that.)&lt;/p&gt;

&lt;pre&gt;
% Channel messages
-define(STATUS_NIBBLE_OFF, 16#8).
-define(STATUS_NIBBLE_ON, 16#9).
% ...
% System common messages
-define(STATUS_SYSEX, 16#F0).
-define(STATUS_SONG_POINTER, 16#F2).
% ...
&lt;/pre&gt;

&lt;p&gt;Next, I dove into reading a MIDI file. Since I only deal with MIDI type 1 files which contain multiple tracks in a single file, that's all I bothered writing. I knew I'd have to read the header and one or more tracks. Erlang code tends to use atoms, tuples, lists, and records to represent many types of data, so I came up with a proposed data format for my sequence data. From the comment for &lt;code&gt;midifile:read/1&lt;/code&gt;, which takes the path to a MIDI file and returns a seq tuple:&lt;/p&gt;
&lt;pre&gt;
%% Returns
%%   {seq, {header...}, ListOfTracks}
%% header is {header, Format, Division}
%% each track is
%%   {track, ListOfEvents}
%% each event is
%%   {event_name, DeltaTime, [values...]}
%% where values after DeltaTime are specific to each event type.
%% If the value is a string, then the string appears instead of
## [values...].
&lt;/pre&gt;

&lt;p&gt;After writing some code that read one byte at a time, I went back to the "Programming with Files" chapter in &lt;a href="http://books.pragprog.com/titles/jaerlang/"&gt;Programming Erlang&lt;/a&gt; by Joe Armstrong and realized that I could slurp all of the MIDI data into memory and randomly access it. This made my code cleaner and faster. It also made pattern matching much easier, because I could write &lt;code&gt;midifile:read_event&lt;/code&gt; with many different pattern-matched arguments but the same arity. Here's the code that reads an event list within a track:&lt;/p&gt;

&lt;pre&gt;
event_list(_F, _FilePos, 0) -&gt;
    [];
event_list(F, FilePos, BytesToRead) -&gt;
    [DeltaTime, VarLenBytesUsed] = read_var_len(file:pread(F, FilePos, 4)),
    {ok, ThreeBytes} = file:pread(F, FilePos+VarLenBytesUsed, 3),
    ?DPRINT("reading event, FilePos = ~p, BytesToRead = ~p, ThreeBytes = ~p~n",
     [FilePos, BytesToRead, ThreeBytes]),
    [Event, EventBytesRead] =
 read_event(F, FilePos+VarLenBytesUsed, DeltaTime, ThreeBytes),
    BytesRead = VarLenBytesUsed + EventBytesRead,
    [Event | event_list(F, FilePos + BytesRead, BytesToRead - BytesRead)].
&lt;/pre&gt;

&lt;p&gt;Let's start at the end: &lt;code&gt;event_list/3&lt;/code&gt; is recursive. It builds the list of events by reading a single event, then calling itself with the remaining bytes to read in the track. If the remaning number of bytes is 0, the first clause (the first two lines above) matches and an empty list is returned. The second, longer clause starts by reading a variable length integer (more about that below). It then reads the next three bytes of the file (again, randomly accessing an in-memory copy of the file). Why three bytes? Because most MIDI events are two or three bytes long. If I need fewer bytes, no harm done. If I need more, I read more. Those three bytes are passed on to &lt;code&gt;read_event/4&lt;/code&gt;, which is a function that is made up of a really long series of clauses, each of which matches a different MIDI event.&lt;/p&gt;

&lt;p&gt;Here are the first few &lt;code&gt;read_event/4&lt;/code&gt; clauses. The first three arguments are the file, current file position, and delta time of the event. These clauses all return an array consisting of the event tuple and the number of bytes used by the event (excepting the length of the delta time).&lt;/p&gt;

&lt;pre&gt;
read_event(_F, _FilePos, DeltaTime,
    &amp;lt;&amp;lt;?STATUS_NIBBLE_OFF:4, Chan:4, Note:8, Vel:8&gt;&gt;) -&gt;
    ?DPRINT("off~n", []),
    put(status, ?STATUS_NIBBLE_OFF),
    put(chan, Chan),
    [{off, DeltaTime, [Chan, Note, Vel]}, 3];
% note on, velocity 0 is a note off
read_event(_F, _FilePos, DeltaTime,
    &amp;lt;&amp;lt;?STATUS_NIBBLE_ON:4, Chan:4, Note:8, 0:8&gt;&gt;) -&gt;
    ?DPRINT("off (using on vel 0)~n", []),
    put(status, ?STATUS_NIBBLE_ON),
    put(chan, Chan),
    [{off, DeltaTime, [Chan, Note, 64]}, 3];
read_event(_F, _FilePos, DeltaTime,
    &amp;lt;&amp;lt;?STATUS_NIBBLE_ON:4, Chan:4, Note:8, Vel:8&gt;&gt;) -&gt;
    ?DPRINT("on~n", []),
    put(status, ?STATUS_NIBBLE_ON),
    put(chan, Chan),
    [{on, DeltaTime, [Chan, Note, Vel]}, 3];
read_event(_F, _FilePos, DeltaTime,
    &amp;lt;&amp;lt;?STATUS_NIBBLE_POLY_PRESS:4, Chan:4, Note:8, Amount:8&gt;&gt;) -&gt;
    ?DPRINT("poly press~n", []),
    put(status, ?STATUS_NIBBLE_POLY_PRESS),
    put(chan, Chan),
    [{poly_press, DeltaTime, [Chan, Note, Amount]}, 3];
read_event(_F, _FilePos, DeltaTime,
    &amp;lt;&amp;lt;?STATUS_NIBBLE_CONTROLLER:4, Chan:4, Controller:8, Value:8&gt;&gt;) -&gt;
    ?DPRINT("controller ch ~p, ctrl ~p, val ~p~n", [Chan, Controller, Value]),
    put(status, ?STATUS_NIBBLE_CONTROLLER),
    put(chan, Chan),
    [{controller, DeltaTime, [Chan, Controller, Value]}, 3];
&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;put&lt;/code&gt; calls store the status and channel in the process dictionary, one of the few places in Erlang that you can modify values. To handle running status bytes, we need to remember the status and channel. I picked the process dictionary as the place to store that.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Added 2007-05-23:&lt;/em&gt; "Running status bytes" are a way to reduce the size of MIDI files. If the status byte is the same as the previous status byte, you can omit it. Also, as a special case a note-on value with a velocity of zero is considered to be a note-off message. I needed code that would recognize that the next byte was not a status byte, and use the previous status byte. I made that an additional &lt;code&gt;read_event/4&lt;/code&gt; clause, like this:&lt;/p&gt;

&lt;pre&gt;
% Handle running status bytes
read_event(F, FilePos, DeltaTime, &amp;lt;&amp;lt;B0:8, B1:8, _:8&gt;&gt;) when B0 &amp;lt; 128 -&gt;
    Status = get(status),
    Chan = get(chan),
    ?DPRINT("running status byte, status = ~p, chan = ~p~n", [Status, Chan]),
    [Event, NumBytes] =
 read_event(F, FilePos, DeltaTime, &amp;lt;&amp;lt;Status:4, Chan:4, B0:8, B1:8&gt;&gt;),
    [Event, NumBytes - 1];
&lt;/pre&gt;

&lt;p&gt;We read the status and channel from the process dictionary and pass them back to &lt;code&gt;read_event/4&lt;/code&gt;, which will use Erlang's pattern matching to go back and find the proper code for that status byte. &lt;em&gt;End of additions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;?DPRINT is a macro that I defined to output when DEBUG is defined and do nothing when it isn't:&lt;/p&gt;

&lt;pre&gt;
% -define(DEBUG, true).
-ifdef(DEBUG).
-define(DPRINT(X, Y), io:format(X, Y)).
-else.
-define(DPRINT(X, Y), void).
-endif.
&lt;/pre&gt;

&lt;p&gt;MIDI encodes certain multi-byte integer values by "variable length encoding" it: splitting it into seven bit chunks and outputting them with the highest-order chunk first. The last, lowest-bit chunk has the high bit set to zero, the earlier, higher chunks have the high bit set to one. Thus zero is encoded as &lt;code&gt;00000000&lt;/code&gt; and 129 is encoded as &lt;code&gt;10000001 00000001&lt;/code&gt;. Notice that the code in &lt;code&gt;event_list/1&lt;/code&gt; always reads the next four bytes; that's because the MIDI spec guarantees that var length numbers are at most four bytes long. Here's the code for &lt;code&gt;read_var_len/1&lt;/code&gt;, which returns a list containing the value and the number of bytes it uses:

&lt;pre&gt;
read_var_len({ok, &amp;lt;&amp;lt;0:1, B0:7, _:24&gt;&gt;}) -&gt;
    [B0, 1];
read_var_len({ok, &amp;lt;&amp;lt;1:1, B0:7, 0:1, B1:7, _:16&gt;&gt;}) -&gt;
    [(B0 bsl 7) + B1, 2];
read_var_len({ok, &amp;lt;&amp;lt;1:1, B0:7, 1:1, B1:7, 0:1, B2:7, _:8&gt;&gt;}) -&gt;
    [(B0 bsl 14) + (B1 bsl 7) + B2, 3];
read_var_len({ok, &amp;lt;&amp;lt;1:1, B0:7, 1:1, B1:7, 1:1, B2:7, 0:1, B3:7&gt;&gt;}) -&gt;
    [(B0 bsl 21) + (B1 bsl 14) + (B2 bsl 7) + B3, 4];
read_var_len({ok, &amp;lt;&amp;lt;1:1, B0:7, 1:1, B1:7, 1:1, B2:7, 1:1, B3:7&gt;&gt;}) -&gt;
    ?DPRINT("WARNING: bad var len format; all 4 bytes have high bit set~n", []),
    [(B0 bsl 21) + (B1 bsl 14) + (B2 bsl 7) + B3, 4].
&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;bsl&lt;/code&gt; means "bit-shift left".&lt;/p&gt;

&lt;p&gt;Only after finishing the MIDI file reader code did I attack the MIDI file writer. For that, I created an in-memory "IO list". An IO list is a list that consts of other IO lists, binaries, or bytes (integers between 0 and 155). The Erlang library method &lt;code&gt;file:write_file/2&lt;/code&gt; writes an IO list with one call.&lt;/p&gt;

&lt;p&gt;Here is the code that writes a MIDI var len value:&lt;/p&gt;

&lt;pre&gt;
var_len(I) when I &amp;lt; (1 bsl 7) -&gt;
    &amp;lt;&amp;lt;0:1, I:7&gt;&gt;;
var_len(I) when I &amp;lt; (1 bsl 14) -&gt;
    &amp;lt;&amp;lt;1:1, (I bsr 7):7, 0:1, I:7&gt;&gt;;
var_len(I) when I &amp;lt; (1 bsl 21) -&gt;
    &amp;lt;&amp;lt;1:1, (I bsr 14):7, 1:1, (I bsr 7):7, 0:1, I:7&gt;&gt;;
var_len(I) when I &amp;lt; (1 bsl 28) -&gt;
    &amp;lt;&amp;lt;1:1, (I bsr 21):7, 1:1, (I bsr 14):7, 1:1, (I bsr 7):7, 0:1, I:7&gt;&gt;;
var_len(I) -&gt;
    exit("Value " ++ I ++ " is too big for a variable length number").
&lt;/pre&gt;

&lt;p&gt;This code makes use of guard clauses: expressions that determine whether or not to execute a function clause. Note that the final clause exits. This matches Erlang's philosophy that you should code for the normal case and let errors happen. It might have been more in the Erlang style to leave off the last case entirely, and let the program fail with a "bad match". Perhaps I'll remove it.&lt;/p&gt;

&lt;p&gt;After finishing the writer, I read in a file with the reader and output the Erlang representation using the writer. The result: a few differences, but all due to decisions about running status byte values. The code isn't perfect; it has a long way to go. I've learned a lot, though.&lt;/p&gt;

&lt;p&gt;Next: a Boids simulation in Erlang.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-7451914363978487826?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/7451914363978487826/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=7451914363978487826' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/7451914363978487826'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/7451914363978487826'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/05/erlang-midi-file-readerwriter.html' title='An Erlang MIDI File Reader/Writer'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-7591962466108762062</id><published>2007-05-22T08:46:00.000-04:00</published><updated>2007-05-22T08:50:08.038-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Learning a New Programming Language</title><content type='html'>&lt;p&gt;I'm a self-professed language maven. I love learning new programming languages. Recently, I've been learning &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt;, thanks to &lt;a href="http://books.pragprog.com/titles/jaerlang/"&gt;Programming Erlang&lt;/a&gt; by Joe Armstrong, the online documentation, and the Erlang mailing list.&lt;/p&gt;

&lt;p&gt;I'm a hands-on learner (see &lt;a href="http://jimmenard.blogspot.com/2006/12/just-try-it.html"&gt;Just Try It&lt;/a&gt;). For me, there are a few phases to learning a new language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;Installation and Documentation&lt;/li&gt;
&lt;li&gt;Syntax&lt;/li&gt;
&lt;li&gt;Standard libraries&lt;/li&gt;
&lt;li&gt;Philosophy&lt;/li&gt;
&lt;li&gt;Frameworks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These phases don't occur sequentially; they blur together. For example, when trying to absorb what I call the philosophy of a language, I'll be writing code that forces me to look for how to use the standard libraries and look for existing frameworks.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;overview&lt;/em&gt; phase is when I initially hear about a language. It might be through posts in mailing lists or discussions groups, the various RSS feeds to which I subscribe, or on &lt;a href="http://reddit.com/"&gt;Reddit&lt;/a&gt;. During this phase I tend to form a few first impressions about a language: is it really different? How? Why does it exist? Will it be useful to me in my day-to-day work? Is it worth learning to exercise my brain, or because I will learn new concepts?&lt;/p&gt;

&lt;p&gt;Next comes &lt;em&gt;installation and documentation&lt;/em&gt;. I grab a copy of the language and whatever documentation I can. This is a key step: if I can't install the language or get it running, or if the documentation doesn't give me enough information to get started, then I'm done.&lt;/p&gt;

&lt;p&gt;Learning the &lt;em&gt;syntax&lt;/em&gt; gets me as far as being able to follow along with a book or the documentation. This is also another place for me to be turned off by a language. For example, when I started playing with Python, I decided it wasn't for me for a few reasons: significant whitespace, the __funky__ naming convention for magic OO methods, and having to refer to "self" all the time. Please remember, Pythonistas (all one of you that might read this :-), that I'm talking about my impressions and my decisions, not yours or anybody else's.&lt;/p&gt;

&lt;p&gt;I'm not sure what made me temporarily abandon Scala (see &lt;a href="http://jimmenard.blogspot.com/2007/01/scala-baby-steps.html"&gt;Scala Baby-Steps&lt;/a&gt;) for Erlang. Partly, I was seeing references to Erlang when reading about Scala. Partly, it felt a bit less developed and complete then Erlang. Mostly, I think I'm learning more new things with Erlang.&lt;/p&gt;

&lt;p&gt;Becoming familiar with a language's &lt;em&gt;standard libraries&lt;/em&gt; or classes lets me know what comes "out of the box", as opposed to what I'll have to go look for or build for myself. It also gives me a good sense of the language's approach to solving various kinds of problems, large and small, and at different levels (from one-liners like iterating through a list through larger libraries like parsers or network communications). Again, the libraries can be make-or-break for me: while playing with Python, I found that simple string and regular expression operations were too difficult to find. (Actually, this is a problem with Erlang, too: string operations are all over the place in different libraries. One place for making a string upper case, another for making it lower case.)&lt;/p&gt;

&lt;p&gt;By &lt;em&gt;philosophy&lt;/em&gt; I mean a few different things: what the language is best at (and worst at), what it was designed for, what its strengths and weaknesses are, and what good code in the language looks like. Philosophy often, but not always, goes hand-in-hand with learning the syntax and the standard libraries.&lt;/p&gt;

&lt;p&gt;During this phase, I usually pick for myself a small number of decent-sized tasks. By this time, I've been writing code in the new language for a while, but mostly toy exercises. It's time to write a larger program. Usually I take a project I've already written in a few different languages and port it to the new language. The code starts as a straight port, but as I progress I try to rewrite it using the idioms and techniques offered by the new language.&lt;/p&gt;

&lt;p&gt;By this time, I've usually look for and found a few different language &lt;em&gt;frameworks&lt;/em&gt; on the order of as a Web application server or app development framework. This gives me an idea not only of what's available, but what the community is like.&lt;/p&gt;

&lt;p&gt;Speaking of community, by this time I've probably joined the language's primary mailing list.&lt;/p&gt;

&lt;p&gt;I plan to blog about three different projects I've been working on in Erlang: a MIDI file reader/writer, a &lt;a href="http://en.wikipedia.org/wiki/Boids"&gt;Boids&lt;/a&gt; simulation, and a simple Web application using Erlyweb.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-7591962466108762062?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/7591962466108762062/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=7591962466108762062' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/7591962466108762062'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/7591962466108762062'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/05/learning-new-programming-language.html' title='Learning a New Programming Language'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-4990822685935652698</id><published>2007-01-09T10:28:00.000-05:00</published><updated>2007-05-22T08:43:49.662-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><category scheme='http://www.blogger.com/atom/ns#' term='scala'/><title type='text'>Scala Baby-Steps</title><content type='html'>&lt;p&gt;I'm starting to learn &lt;a href="http://scala.epfl.ch/"&gt;Scala&lt;/a&gt;. It's a
  statically- and strongly-typed functional language that compiles to Java
  byte code and integrates well with Java.&lt;/p&gt;

&lt;p&gt;One of the things I'm struggling with is the language's strong static typing. Scala is nice in
  that it tries very hard to infer types. That means the programmer doesn't
  spend much time typing "wasteful" type declarations. If Scala didn't have
  that, I would have run away screaming already.&lt;/p&gt;

&lt;p&gt;I've figured out how to connect to a MySQL database. There are a few
  database access methods that are being developed in Scala. The one that ships
  with the language is called "dbc". It comes with a PostgreSQL connection
  interface but no MySQL version. Here's my MySQL version along with some
  simple code that performs a select. (I'd move the connection information
  into a properties file if I were to really use this code.)&lt;/p&gt;

&lt;pre&gt;
import scala.dbc._
import scala.dbc.Syntax._
import scala.dbc.syntax.Statement._
import java.net.URI

object MysqlVendor extends Vendor {
  val uri = new URI("jdbc:mysql://localhost:3306/my_database_name")
  val user = "my_database_user"
  val pass = "my_database_password"
  
  val retainedConnections = 5
  val nativeDriverClass = Class.forName("com.mysql.jdbc.Driver")
  val urlProtocolString = "jdbc:mysql:"
}

object BulkUploadRunner extends Application {
  val db = new Database(MysqlVendor)

  val rows = db.executeStatement {
    select fields ("email_id" of characterVarying(32)) from ("bulk_uploads")
  }
  for (val r &lt;- rows;
       val f &lt;- r.fields) {
    Console.println(f.content.nativeValue) // or .sqlValue
  }

  db.close
}
&lt;/pre&gt;

&lt;BlogItemURL&gt;
   &lt;a href="&lt;$BlogItemURL$&gt;"&gt;Link&lt;/a&gt;
&lt;/BlogItemURL&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-4990822685935652698?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/4990822685935652698/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=4990822685935652698' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/4990822685935652698'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/4990822685935652698'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2007/01/scala-baby-steps.html' title='Scala Baby-Steps'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-1910673941794722478</id><published>2006-12-08T10:14:00.000-05:00</published><updated>2007-01-09T10:33:16.396-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='xhtml'/><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='firefox'/><category scheme='http://www.blogger.com/atom/ns#' term='ajax'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='css'/><title type='text'>Firebug</title><content type='html'>&lt;p&gt;&lt;a href="http://www.getfirebug.com/"&gt;Firebug&lt;/a&gt; is an incredibly useful Firefox extension for developers. You can debug JavaScript, edit HTML and CSS, inspect the DOM, determine the download times of all of the elements on a page, inspect headers, and much more.&lt;/p&gt;

&lt;p&gt;Firebug 1.0 beta has just been released. I can't say enough good things about this extension. If you are a Web developer, it really should be part of your tool set.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-1910673941794722478?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://www.getfirebug.com/' title='Firebug'/><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/1910673941794722478/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=1910673941794722478' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/1910673941794722478'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/1910673941794722478'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2006/12/firebug.html' title='Firebug'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-991775354193842015</id><published>2006-12-05T08:51:00.000-05:00</published><updated>2006-12-05T08:54:06.646-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Java's Enhanced For Loop Mystery</title><content type='html'>&lt;p&gt;I like Java 5's new enhanced for loop. It makes the code that iterates
  over collections and arrays less verbose. Anything that reduces Java's
  verbosity is a good thing in my book.&lt;/p&gt;

&lt;p&gt;Yesterday I wondered if the Java 5 enhanced for loop calls the collection
  expression once or every time through the loop. I searched for the answer
  online, and couldn't find one. Every example I found assigned the
  collection or array to a variable first and then used that variable in the
  expression, like this:&lt;/p&gt;

&lt;pre&gt;
  String[] list = {"a", "b", "c"};
  for (String item : list) {
    // ...
  }
&lt;/pre&gt;

&lt;p&gt;What I was looking for was a statement like, "the list expression is only
  evaluated once". After a few minutes, I realized that it would be faster to
  write a small program to determine the answer.
  (See &lt;a href="http://jimmenard.blogspot.com/2006/12/just-try-it.html"&gt;Just
  Try It&lt;/a&gt;.)&lt;p&gt;

&lt;pre&gt;
import java.util.*;

public class Test {

  public Collection&amp;lt;String&amp;gt; stringCollection() {
    System.out.println("I'm creating a new list now.");
    // Don't get me started on the verbosity of the next line...
    ArrayList&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;String&amp;gt;();
    list.add("a");
    list.add("b");
    list.add("c");
    return list;
  }

  public static void main(String[] args) {
    Test t = new Test();
    for (String s : t.stringCollection())
      System.out.println(s);
  }
}
&lt;/pre&gt;

&lt;p&gt;With a simple &lt;code&gt;javac Test.java &amp;amp;&amp;amp; java Test&lt;/code&gt; I had the answer:
  &lt;code&gt;stringCollection()&lt;/code&gt; is only called once.&lt;p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-991775354193842015?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/991775354193842015/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=991775354193842015' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/991775354193842015'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/991775354193842015'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2006/12/javas-enhanced-for-loop-mystery.html' title='Java&apos;s Enhanced For Loop Mystery'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-8985919473193003326</id><published>2006-12-05T08:44:00.000-05:00</published><updated>2006-12-05T08:50:18.006-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Just Try It</title><content type='html'>&lt;p&gt;I love answering programming questions. It lets me feel like a grizzled
veteran sharing my arcane knowledge with those eager to learn. (Yes, I'm
delusional.) If I don't know the answer, I enjoy finding out.&lt;/p&gt;

&lt;p&gt;Most people don't know all the arcane language rules of every language they
use. As I get older, I'm less inclined to memorize a language spec or
an &lt;acronym title="Application Programming Interface"&gt;API&lt;/acronym&gt;. For
example, yesterday I wondered if the Java 5 enhanced for loop calls the
collection expression once or every time through the loop. I searched for the
answer online, and couldn't find one. After a few minutes, I realized that it
would be faster to write a small program to determine the answer. Within two
minutes, I knew. (I'm saving the answer and the code for another blog
post.)&lt;/p&gt;

&lt;p&gt;If you have a question about a language rule or the use of a library, there
is more than one way to find the answer: read the documentation, ask somebody
else, or try it. If at all possible, Just Try It&amp;trade;&amp;mdash;write a small
program that answers your question. Even better, if your language has
a &lt;acronym title="Read Eval Print Loop"&gt;REPL&lt;/acronym&gt; or even accepts input
from stdin, then type a few lines of code interactively.&lt;/p&gt;

&lt;p&gt;Ruby comes with &lt;code&gt;irb&lt;/code&gt;, the Interactive Ruby Browser. Every Lisp
has a REPL. Smalltalk has the Workspace. Shell languages have the shell.&lt;/p&gt;

&lt;p&gt;When you Just Try It, you gain the immediate satisfaction of knowing the
answer. You also use a different part of your brain then when you read the
answer online; I claim you absorb the answer more thoroughly if you write a
small code snippet. You might want to keep a collection of these code snippets
for yourself. I have a personal Wiki (an Emacs Wiki, of course) where I store
these sorts of things if they're useful enough. There are also a few snippet
collection Web sites out there, such
as &lt;a href="http://www.bigbold.com/snippets/"&gt;Code Snippets&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/699020937694156193-8985919473193003326?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/8985919473193003326/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=8985919473193003326' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8985919473193003326'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/8985919473193003326'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2006/12/just-try-it.html' title='Just Try It'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-699020937694156193.post-7373750766118337669</id><published>2006-12-05T08:28:00.000-05:00</published><updated>2006-12-05T08:49:24.595-05:00</updated><title type='text'>Settling In</title><content type='html'>&lt;p&gt;Welcome to the new home of Shiny Things, a blog about programming, programming languages, tools, frameworks, programming productivity tips, and whatever else catches my eye.&lt;/p&gt;

&lt;p&gt;The &lt;a href="http://www.io.com/~jimm/blog/"&gt;old home of Shiny Things&lt;/a&gt; will remain. One reason I moved Shiny Things here is to allow comments. I haven't decided if I will copy some or all of the posts.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/699020937694156193-7373750766118337669?l=jimmenard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimmenard.blogspot.com/feeds/7373750766118337669/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=699020937694156193&amp;postID=7373750766118337669' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/7373750766118337669'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/699020937694156193/posts/default/7373750766118337669'/><link rel='alternate' type='text/html' href='http://jimmenard.blogspot.com/2006/12/settling-in.html' title='Settling In'/><author><name>Jim Menard</name><uri>http://www.blogger.com/profile/16242400565135394692</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://3.bp.blogspot.com/-orFc8c3jyQc/Tl5-jBuun2I/AAAAAAAAA8c/rVBa5-sGgq0/s1600/bearded_jim.jpg'/></author><thr:total>0</thr:total></entry></feed>
