<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" gd:etag="W/&quot;CkECRXwyeyp7ImA9WhRbEU8.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440</id><updated>2012-02-01T13:24:24.293-05:00</updated><category term="ruby" /><category term="privacy" /><category term="blogger" /><category term="css" /><category term="terracotta" /><category term="blogging" /><category term="clojure" /><category term="wordpress" /><category term="security" /><category term="rails" /><title>Paul Stadig</title><subtitle type="html" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://paul.stadig.name/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>23</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/PaulStadig" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="paulstadig" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;CEENQ3gycCp7ImA9Wx9SF04.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-2175350653390749843</id><published>2010-12-07T06:10:00.018-05:00</published><updated>2010-12-07T09:38:12.698-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-07T09:38:12.698-05:00</app:edited><title>Thou Shalt Not Lie: git rebase, ammend, squash, and other lies</title><content type="html">&lt;p&gt;As I've used git more, and used more advanced features, my opinions about the merit and uses of certain features have changed.  At work we're constantly re-evaluating our git workflow in light of the problems we encounter with the way we're doing things. I may yet re-evaluate these opinions, but rest assured they come not from some theoretical nit-pick, but from real experience.&lt;/p&gt;

&lt;h3&gt;Rewriting History is a Lie&lt;/h3&gt;
&lt;p&gt;Using git to rewrite history is a sin.  It's called lying.  Don't do it.  With git there are several forms in which lying comes, but usually you know you're lying if you break the functionality of &lt;code&gt;`git branch --contains some-branch`&lt;/code&gt;, &lt;code&gt;`git blame`&lt;/code&gt;, &lt;code&gt;`git bisect`&lt;/code&gt;, or if you have to use &lt;code&gt;`git push -f origin some-branch`&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Squash merging is a lie&lt;/h3&gt;
&lt;p&gt;A squash merge (&lt;code&gt;`git merge --squash some-branch`&lt;/code&gt;) takes all the commits from a topic branch, combines them into a single commit, and applies that commit.  The history now looks as if you had a flash of brilliance, made a ton of changes, and did everything right the first time.  It makes you look good, but it's a lie.&lt;/p&gt;

&lt;p&gt;Since, you have not actually merged any of the work you did on the topic branch, but instead have merged another &lt;em&gt;totally new&lt;/em&gt; commit, &lt;code&gt;`git branch --contains some-branch`&lt;/code&gt; cannot tell you that you have merged your branch anywhere.  When a QA person or your boss says, "Hey, is some-feature {merged into QA, deployed}" you have to resort to &lt;code&gt;`git log`&lt;/code&gt; spelunking.&lt;/p&gt;

&lt;p&gt;Also, anytime you are creating a &lt;em&gt;new commit&lt;/em&gt; with the same changes as another commit, you are destroying &lt;code&gt;`git blame`&lt;/code&gt;'s ability to tell you who to flog publicly.  And as we all know, public floggings are the lifeblood of software development teams.&lt;/p&gt;

&lt;p&gt;Finally, when you squash merge, the totally awesome &lt;a href="http://progit.org/2010/03/08/rerere.html"&gt;rerere&lt;/a&gt; feature will not help you re-resolve conflicts.  Instead you will have to re-resolve conflicts when you cherry pick the squash commit, or when you squash merge your topic branch to the UA branch or to master.  Why?  Because &lt;code&gt;rerere&lt;/code&gt; depends on being able to detect that you are resolving conflicts between the same SHAs as before, but every time you squash merge it creates a &lt;em&gt;totally new&lt;/em&gt; commit. It creates a totally new commit even if you squash merge, &lt;code&gt;`git reset --hard HEAD^`&lt;/code&gt;, and then immediately squash merge again.&lt;/p&gt;

&lt;h3&gt;Rebasing is a lie&lt;/h3&gt;
&lt;p&gt;Rebasing using &lt;code&gt;`git rebase foo`&lt;/code&gt; allows you to rebase your topic branch on foo, instead of whatever it was based on before.  This makes it look like you were working from foo the whole time.  However, each commit to your topic branch was birthed in a context and by a sequence of events that was unique to that time and that topic branch.  You are yanking those commits out of their context and putting them into a &lt;em&gt;totally new&lt;/em&gt; context.&lt;/p&gt;

&lt;p&gt;Rebasing is effectively a retroactive merge.  It is pretending that you "merged" foo into your topic branch 3 days ago, but you didn't.  You merged it in today, and you are lying to everyone.&lt;/p&gt;

&lt;p&gt;In fact, the commits from your topic branch don't exist anymore, because every single rebased commit gets remade into a &lt;em&gt;totally new&lt;/em&gt; commit with a &lt;em&gt;totally different&lt;/em&gt; SHA. Pick any one of those rebased commits: Does the commit message make sense anymore?  Does the code compile?  Do the tests pass?&lt;/p&gt;

&lt;p&gt;The answer to these last two questions will tell you whether you can use the totally awesome &lt;code&gt;bisect&lt;/code&gt; feature of git.  This feature does a binary search through your history to help you discover exactly where some bug or problem was introduced into your code.  If you cannot compile and run tests on every commit, then you cannot use &lt;code&gt;`git bisect`&lt;/code&gt;, which is a shame.&lt;/p&gt;

&lt;p&gt;Being able to compile and run tests for each commit is also useful for resolving conflicts.  Say I'm trying to decide whether your change will work with my code, or my change with your code, or maybe some totally different code that "resolves" the conflict, how can I know "resolved" means resolved?  The most basic thing I can do (though it's not foolproof) is compile the code and run the tests, but if your code doesn't even compile or run the tests by itself, then I can't intelligently resolve the conflict.  Your lie has hurt me and the rest of the team.&lt;/p&gt;

&lt;p&gt;Finally, if for some reason you wanted to cherry-pick a commit, but it doesn't compile or run the tests, then cherry-picking it into another branch would break that branch.  Thanks for that!  Your lie has now become destruction of property, which is a misdemeanor.&lt;/p&gt;

&lt;h3&gt;Amending a commit is a lie&lt;/h3&gt;
&lt;p&gt;Amending a commit (&lt;code&gt;`git commit --amend`&lt;/code&gt;) to add changes, or to change the commit message is a lie.  It breaks &lt;code&gt;`git branch --contains some-branch`&lt;/code&gt;.  It forces you to do a &lt;code&gt;`git push -f origin some-branch`&lt;/code&gt;.  It's a sin.  See above arguments, enough said.&lt;/p&gt;

&lt;h3&gt;Selective, retroactive commits are lies&lt;/h3&gt;
&lt;p&gt;In this last form of lying git is not an accomplice, because you are lying &lt;em&gt;to&lt;/em&gt; git.  What happens here is that you have been working for a while (20 minutes, 30 minutes, an hour, whatever), and you decide to commit your work, but instead of committing all of your work as you have come to it naturally, you decide to break your work up into several small, "logical" commits.  This makes you look good, but it's a lie.&lt;/p&gt;

&lt;p&gt;You've changed both "foo.clj" and "bar.clj" and you think there is no dependency between them, so you commit "foo.clj" separately from "bar.clj".  The problem is that you don't &lt;em&gt;really&lt;/em&gt; know this unless you compile the code and run the tests.  Since you don't know for sure that these selective commits can compile and run the tests, you've broken &lt;code&gt;`git bisect`&lt;/code&gt;, undermined trust in conflict resolution, and break my stuff if I cherry-pick your commit.&lt;/p&gt;

&lt;p&gt;You may think that your commits are "logical", but I would say if you are not committing your work the way that you come to it naturally, then it is not logical.  Sure you started solving problem A and then discovered problem B, if they are related and you have to solve problem B in order to solve A, then commit the changes together.  They are related so its logical to commit them at the same time.&lt;/p&gt;

&lt;p&gt;If you want to solve them separately, then make a WIP commit or a stash, create another branch and solve B, then come back to your original branch and rebase it on B (gasp, see below for explanations of this inconsistency!).&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;There are several awesome features of git: &lt;code&gt;blame&lt;/code&gt;, &lt;code&gt;branch --contains&lt;/code&gt;, &lt;code&gt;rerere&lt;/code&gt;, and &lt;code&gt;bisect&lt;/code&gt;.  These features are borked if you lie.  If you're going to lie, you may as well use SVN, since these awesome features don't exist there anyway.  But don't do that!  Let's just all agree not to lie.&lt;/p&gt;

&lt;h3&gt;Epilogue&lt;/h3&gt;
&lt;p&gt;Some people may get the impression that I do not think you should ever use &lt;code&gt;`git rebase`&lt;/code&gt;, &lt;code&gt;`git commit --amend`&lt;/code&gt;, or &lt;code&gt;`git merge --squash`&lt;/code&gt;, but that would not be true.  These are powerful tools that may be necessary sometimes, but should be used sparingly and judiciously.  Perhaps its OK to lie sometimes.  If someone comes up to you and says, "How does this make me look?"  You choose your words carefully.&lt;/p&gt;

&lt;p&gt;One case for history rewriting is integration branches. At work we have integration branches for qa, ua, etc.  We have a workflow where we move a single feature through the pipeline kanban style, so we have to be able to merge a feature to an integration branch, rebase it out, etc.  In this case no one has an expectation that they should be able to see a clean history for these branches, and no one expects to base their work on one of them.&lt;/p&gt;

&lt;p&gt;Other than integration branches, a good rule of thumb is that you should not rewrite history for things that are already push out into the world.  This would limit these rewriting tools to uses locally to "fix" things up before pushing them.  I still cannot whole-heartedly advise use of rewriting in this case, but if you need to, and the rewriting your doing is limited enough that you're sure the new commits will still compile and pass tests, then fine.  Just do it carefully, and don't make it habitual. The End.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-2175350653390749843?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/2175350653390749843/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=2175350653390749843" title="12 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/2175350653390749843?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/2175350653390749843?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2010/12/thou-shalt-not-lie-git-rebase-ammend.html" title="Thou Shalt Not Lie: git rebase, ammend, squash, and other lies" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>12</thr:total></entry><entry gd:etag="W/&quot;CUEBQnw_fip7ImA9WxFbEUg.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-4231207780770707367</id><published>2010-07-03T06:15:00.003-04:00</published><updated>2010-07-03T06:40:53.246-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-07-03T06:40:53.246-04:00</app:edited><title>The game of Risk for children</title><content type="html">&lt;p&gt;We had family game night last night, and my 4yo son wanted to play Risk.  He likes the little army men, and before we had just played with the army men on the board, but I decided to invent a simplified game for him to play.  I just made it up as we went along, but I was pleased with how it turned out.&lt;/p&gt;

&lt;p&gt;Each person takes a turn drawing a card from the pile.  If a person draws a wild card, then they can place their army on any empty country, or if there are no empty countries, they can pick any inhabited country and do "battle."  During a battle each player rolls one die, and the player with the highest number wins (in the case of a tie you roll again). The winner either keeps the country, or kicks out whomever was inhabiting the country.&lt;/p&gt;

&lt;p&gt;When a player picks a country card, if the country on the card is empty, then they place an army on the country. If the country is not empty they do battle.&lt;/p&gt;

&lt;p&gt;Each player keeps the cards they draw, and during their turn if they have three of a kind, then they get to place an extra army on any empty country they choose, or if there are no empty countries, then they can choose an inhabited country and do battle.&lt;/p&gt;

&lt;p&gt;The game ends when the pile of cards is exhausted, and there is an army on every country.  My thought was that at the end we would count up the number of countries that each player had, and the one with the most would win.  We didn't get to do that last night, though, because my son wanted to drive a cannon through all the countries on the board making all the armies fly everywhere (who can blame him!).&lt;/p&gt;

&lt;p&gt;I don't think there is any strategy to this game.  You are basically subject to the randomness of the cards you draw, and to the dice in the case of battle.  You could assign extra points for holding an entire continent at the end of the game, but I'm not sure there are enough opportunities to choose on which country you'd like to place an army.&lt;/p&gt;

&lt;p&gt;So there may be room for improvement, however, it was surprisingly entertaining, and it kept the attention of my son.  :)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-4231207780770707367?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/4231207780770707367/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=4231207780770707367" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/4231207780770707367?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/4231207780770707367?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2010/07/game-of-risk-for-children.html" title="The game of Risk for children" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DUcERnk-fSp7ImA9WxFVEkg.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-5190345530561042839</id><published>2010-06-10T12:30:00.010-04:00</published><updated>2010-06-11T07:50:07.755-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-11T07:50:07.755-04:00</app:edited><title>The Flerb Paradox</title><content type="html">&lt;p&gt;Chas Emerick has &lt;strike&gt;ranted against Emacs&lt;/strike&gt; &lt;a href="http://muckandbrass.com/web/display/~cemerick/2010/06/07/Results+from+the+State+of+Clojure%252C+Summer+2010+Survey"&gt;conducted a survey&lt;/a&gt; of Clojure usage.  That's right, in the middle of a &lt;em&gt;friggin&lt;/em&gt; survey of Clojure usage he took the time to bash Emacs, and I'm kinda fed up with Chas' Emacs hate.  I'm a heavy Emacs user, and I think its the bees knees, and I would like to correct some errors in Chas' article.&lt;/p&gt;

&lt;h3&gt;Fact #1: Learning Emacs is not necessary for learning Clojure&lt;/h3&gt;
&lt;p&gt;No one has ever said it is.  It's a flat-out false statement.  Chas does not use Emacs to write Clojure.  Neither do 30% of Clojure users according to his survey results, and when you look at the comments on Brian Carper's &lt;a href="http://briancarper.net/blog/emacs-isnt-for-everyone"&gt;article&lt;/a&gt;, you see many people saying they gave up trying to learn Emacs, and went with another editor.&lt;/p&gt;

&lt;h3&gt;Fact #2: No one is pushing Emacs&lt;/h3&gt;
&lt;p&gt;The &lt;a href="http://clojure.org/"&gt;clojure.org&lt;/a&gt; homepage does not say it is a syntax error to write Clojure code in an editor other than Emacs.  In fact, I remember a time when I almost unsubscribed from the Clojure mailing list because there was so much talk about VimClojure (not that I hate vim, it was just noise to me).  Contrary to Chas' belief, people are not running around thumping their Emacs.  Chas probably just sees Emacs mentioned with Clojure in blogs and tweets and IRC and gets the impression that people are being bigots, but as revealed by his survey there is a simple explanation for this: 70% of us are using Emacs.  In fact, if anyone is obsessed with Emacs its Chas, he seems incapable of talking about Clojure IDEs (or perhaps just Clojure) without ranting against Emacs.&lt;/p&gt;

&lt;h3&gt;Fact #3: There &lt;em&gt;are&lt;/em&gt; alternatives&lt;/h3&gt;
&lt;p&gt;Frankly, if I was the developer of Counterclockwise, Enclojure, La Clojure, or VimClojure (that's right four other, non-Emacs editing environments for Clojure!), I would probably be hurt my Chas' comments.  He looks at Emacs, which he finds too difficult to be worth his time, and concludes that there is no decent editing environment for Clojure.  He even goes so far as to offer to pay someone to develop a green-field environment for him.&lt;/p&gt;

&lt;h3&gt;Fact #4: People hate Clojure's syntax more than any particular editor&lt;/h3&gt;
&lt;p&gt;Clojure's Lisp syntax and abundance of parenthesis is mentioned as a weakness/blind spot in the survey results just as many times, if not more, than IDE complaints.  Many people think syntax is scaring off newbies more often than not.  I think that is a natural reaction.  I too was put off by the parenthesis at first, but once Lisp clicks for you, you realize that the parenthesis are necessary for its power.  Someone who puts off learning Clojure because its syntax looks weird and different is missing out on the power that is enabled by that "weirdness."  You have to be willing to try something different, completely different, and go through some pain to gain this incredible power.&lt;/p&gt;

&lt;h3&gt;The Flerb Paradox&lt;/h3&gt;
&lt;p&gt;That leads me to my last point. I'm not sure how to say this without it coming across as me being a jerk.  Let me just say that I'm not out to attack Chas or anyone else personally.  I don't think Chas is an idot.  I don't hate him.  I just wish he'd stop riding his anti-Emacs hobby horse.  However, what I'm about to say is controversial: editing environments vary in their productivity enhancement.  This is a corollary to the Blub Paradox that I call the Flerb Paradox.&lt;/p&gt;

&lt;p&gt;Editing environments lie along a continuum of productivity enhancement from a plain text editor on up.  The Flerb editing environment is a fictional environment that is somewhere in the middle of the continuum.  A user of the Flerb editing environment looks down one end of the continuum at Notepad and balks at its lack of feature x (syntax highlighting, code completion, etc.).  He knows he is looking down the continuum and his editor makes him more productive than those below it.  Otherwise, why wouldn't he just use notepad?&lt;/p&gt;

&lt;p&gt;However, when he looks up the power continuum at Emacs he doesn't realize he's looking up the continuum.  He only sees something strange and confusing. He doesn't understand why anyone would want to use something like Emacs, because Emacs can do everything Flerb can do, but it has all this hairy stuff thrown in for no reason.  It has different key bindings and Meta keys and buffers!  It seems to be designed to confuse its users.  This is the Flerb paradox.&lt;/p&gt;

&lt;p&gt;Now, am I saying that Emacs is the best editor that could possibly be invented?  No.  What I'm saying is that it's the most productive editor that is available.  There's still room for other editing environments.  Perhaps one of them will turn out to be more productive than Emacs. There are plenty of people out there who would rather use something other than Emacs, and I'm fine with that.  I think they won't be as productive as possible, but that's their choice.&lt;/p&gt;

&lt;p&gt;Learning Emacs was tough.  It was painful. And just like most tough things in life, it was worth it.  But this is coming from someone who switched to the Dvorak keyboard layout, and taught himself to use the mouse with his left hand.  I like to do things for the challenge, because it keeps me sharp.  I fear the day I become dull and complacent.&lt;/p&gt;

&lt;p&gt;So, while I said Clojure developers aren't Emacs bigots, I've turned into one, and it's Chas' fault :-), just like superheros create supervillians that create superheros.  If Chas hadn't railed against Emacs so much I wouldn't have turned into the supervillian I am today.  &gt;;-)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-5190345530561042839?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/5190345530561042839/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=5190345530561042839" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5190345530561042839?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5190345530561042839?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2010/06/flerb-paradox.html" title="The Flerb Paradox" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>3</thr:total></entry><entry gd:etag="W/&quot;CUYMSXsyeCp7ImA9WxBbF0g.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-3438420333983035090</id><published>2010-03-16T11:19:00.002-04:00</published><updated>2010-03-16T11:26:28.590-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-03-16T11:26:28.590-04:00</app:edited><title>Hiring a Rails Lead</title><content type="html">&lt;p&gt;My company (&lt;a href="http://www.sonian.com/"&gt;Sonian&lt;/a&gt;) is hiring a Rails Lead position. This is a remote, work-from-home, position, and it's an awesome situation...I love it! It is a team of excellent developers that are fun to work with.&lt;/p&gt;

&lt;p&gt;Preferably an applicant:
&lt;ul&gt;&lt;li&gt;has been a team lead before&lt;/li&gt;
    &lt;li&gt;has worked on a distributed team before&lt;/li&gt;
    &lt;li&gt;has worked in a pair programming environment before&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;This is a full-time position for &lt;i&gt;US based individuals only&lt;/i&gt; at a well funded startup that develops and sells an e-mail archiving solution with several paying customers. In addition to Rails, we're using modern tech like Git, Chef, Amazon AWS, Clojure, and distributed file systems. We're indexing terabytes of data for searching. It's a cool and serious application!&lt;/p&gt;

&lt;p&gt;If you are interested in applying, please send a resume to &lt;a href="mailto:jobs@sonian.com"&gt;jobs@sonian.com&lt;/a&gt;. If you have any other specific questions about Sonian or the position, feel free to contact me at &lt;a href="mailto:paul@stadig.name"&gt;paul@stadig.name&lt;/a&gt; or call me at 703-634-9339.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-3438420333983035090?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/3438420333983035090/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=3438420333983035090" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/3438420333983035090?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/3438420333983035090?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2010/03/hiring-rails-lead.html" title="Hiring a Rails Lead" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;D0cCQH45eSp7ImA9WxBWEk8.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-1656523129550708431</id><published>2010-02-03T13:40:00.005-05:00</published><updated>2010-02-03T14:24:21.021-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-02-03T14:24:21.021-05:00</app:edited><title>Eva Cassidy</title><content type="html">&lt;p&gt;I'm probably late to the game with this one, since most of her recordings date from the late 1990's, and her album skyrocketed to number 1 in England around 2000, but I have just discovered an amazing musician.&lt;/p&gt;

&lt;p&gt;Eva Cassidy grew up just outside Washington DC in Bowie, MD. She struggled with being extremely shy, and didn't like to be in the spotlight.  She sang as a background singer for other musicians, and occasionally played at Blues Alley in DC.  She recorded demos that she sent to labels, but her talents were too wide ranging.  Record labels had a hard time imagining how to sell an artist that excelled a jazz, folk, pop/rock, R&amp;B, and gospel.&lt;/p&gt;

&lt;p&gt;In 1996, after some success collaborating with Chuck Brown on "The Other Side" and releasing a live album from some gigs she did at Blues Alley, she moved to Annapolis, MD and took a job painting murals at elementary schools.  The summer of 1996 she was experiencing hip pain that she assumed was from using the stepladder at work.  X-rays revealed she had cancer throughout her body.  A few short months later, at the age of 33, she died.&lt;/p&gt;

&lt;p&gt;Eva had a beautiful, clear voice, and for being extremely shy and afraid of the spotlight, she poured herself and her passion into her performances.  Just search &lt;a href="http://www.youtube.com/results?search_query=eva+cassidy&amp;search_type=&amp;aq=f"&gt;YouTube&lt;/a&gt; for videos, watch them, and you'll see what an amazing talent she had.&lt;/p&gt;

&lt;p&gt;Her fame and success has come posthumously.  When a DJ in England played her song on the radio, her album became a smash hit, and more and more people have been discovering her music.  Perhaps part of the attention her music has gotten is because of her tragic story, but I believe her music stands on its own, and I'm glad to have discovered it.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-1656523129550708431?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/1656523129550708431/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=1656523129550708431" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/1656523129550708431?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/1656523129550708431?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2010/02/eva-cassidy.html" title="Eva Cassidy" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CkIFRHcycCp7ImA9WxNbGUQ.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-2300280514812083664</id><published>2009-11-21T14:18:00.023-05:00</published><updated>2009-11-23T10:35:15.998-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-23T10:35:15.998-05:00</app:edited><title>The Great Chef: A Parable</title><content type="html">&lt;p&gt;There was once a boy who loved food.  He would sit for hours and read anything that had to do with food and cooking, including the instruction manuals for the kitchen appliances.  The boy began to cook by trying out simple recipes.  He would make the same recipes over and over and over, until he understood how each part contributed to the whole.  His father noticed the boy's near obsession with cooking, and arranged for the boy to apprentice a chef.&lt;/p&gt;

&lt;p&gt;Though the chef was not particularly masterful, he was a good chef.  The boy was enthralled.  He studied every minute detail of the kitchen, every movement of the chef.  He studied the way the chef assigned tasks to the others that worked in the kitchen.  He studied the way the chef chose his ingredients, and prepared his recipes.  The boy became familiar with every tool at the chef's disposal, and eagerly did every job assigned to him, from chopping vegetables to mopping the floor.&lt;/p&gt;

&lt;p&gt;Eventually, the boy became a man and he decided to enter cooking school.  The first few years were boring drudgery to him, and he learned almost nothing.  He had already experienced the inner workings of a disciplined kitchen, and he had continued to be a voracious reader of anything to do with food and cooking.  However, in the later years of cooking school a new world was opened to him.  He learned cooking techniques only few in the world understood.  He learned the potent and exotic flavor of each spice.  He learned the magic and the music of cooking.&lt;p&gt;

&lt;p&gt;Delicate dishes were like a symphony, each part had its purpose, and when everything came together the person eating it shared in some great truth, it was almost...mystical.  The man had such a profound and heartfelt appreciation for food and cooking, that he found he couldn't even explain it to anyone else.  In fact, the only people he could explain it to were others who had the same deep appreciation for food and cooking.&lt;/p&gt;

&lt;p&gt;The man excelled at cooking school.  His instructors and professors loved him because he was a passionate student.  He eventually felt as though his professors were his peers, instead of his teachers.  He even taught them a few things as he experimented with creating some truly unique recipes.&lt;/p&gt;

&lt;p&gt;Finally, the man graduated from cooking school with the highest honors that could be achieved.  He had become a master craftsman, an artisan.  He had become The Great Chef.  He struck out on his own to start a restaurant.  His restaurant soon gained acclaim as everyone recognized his genius.  Reservations had to be made over a year in advance.&lt;/p&gt;

&lt;p&gt;The Great Chef decided that he would try something that would tax his skills to the breaking point.  He had always toyed with the idea of creating this one dish that everyone said was impossible to create.  He set his mind to it.  If he cooked the tomatoes just so it would bring out the flavor he desired, and that would combine perfectly with the mushrooms.  The combination of spices would perfectly complement the other ingredients.  He sought out only the freshest meats and vegetables that were at the peak of ripeness, and at great expense.&lt;/p&gt;

&lt;p&gt;All of The Great Chef's life's preparation had led up to this moment.  By following his finely tuned intuition, he had created a recipe where no single ingredient could be removed without causing the flavor of the whole dish to collapse, because each ingredient depended on the others to draw out and complement its flavors.  He only had to make the recipe once to know that it was perfect.  He had done the impossible!  He had proven beyond any doubt that he had no equal, and in the process he had created a new branch of the culinary arts.&lt;/p&gt;

&lt;p&gt;One day, a man came to have dinner at The Great Chef's restaurant.  He wasn't a connoisseur but he liked to try strange new things, and when someone told him The Great Chef's new dish was the best, he had to have it.  He had made a reservation a year ago, and had come in from out-of-town expressly for this purpose.  When it came time to order, the man said he would like to try the new dish, but as he read the description he said, "Oh...I don't like tomatoes.  Can you make it without the tomatoes?"&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-2300280514812083664?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/2300280514812083664/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=2300280514812083664" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/2300280514812083664?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/2300280514812083664?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/11/great-chef-parable.html" title="The Great Chef: A Parable" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DkIDRX08fip7ImA9WxNbF0k.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-4013656518975832343</id><published>2009-11-20T12:52:00.010-05:00</published><updated>2009-11-20T14:16:14.376-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-20T14:16:14.376-05:00</app:edited><title>The Programming of Philosophy</title><content type="html">&lt;p&gt;Just recently I watched Rich Hickey's &lt;a href="http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey"&gt;presentation&lt;/a&gt; from the JVM Languages Summit 2009.  It is a very interesting and thought provoking presentation, and well worth viewing.  In it he takes from philosophy an understanding of time, state, and identity and applies it to the design of computer programming languages and models for concurrency.&lt;/p&gt;

&lt;p&gt;Rich's presentation is also a further proof that no science (to include computer science) is philosophically or religiously neutral&lt;sup&gt;&lt;a href="#fn1"&gt;1&lt;/a&gt;&lt;/sup&gt;.  Computer science in particular is one of the more "philosophical" sciences.  We model and reason about information in its purest and most elemental forms.  We learn &lt;a href="http://en.wikipedia.org/wiki/De_Morgan's_laws"&gt;De Morgan's laws&lt;/a&gt; in CS classes for goodness sake!&lt;/p&gt;

&lt;p&gt;The connection between philosophy, religion, and computer science is illustrated best in the field of artificial intelligence, where we must answer questions like:
&lt;ul&gt;
&lt;li&gt;What is intelligence?&lt;/li&gt;
&lt;li&gt;Are human brains nothing more than biochemical computers?&lt;/li&gt;
&lt;li&gt;Can an electronic computer model intelligence accurately?&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;I find it telling, that most AI researchers today punt on these issues.  They have moved away from trying to define and model some theory of general intelligence, and moved towards statistical techniques, and creating agents that "act rationally" by imitating human behavior.  However, even taking a pragmatic approach&lt;sup&gt;&lt;a href="#fn2"&gt;2&lt;/a&gt;&lt;/sup&gt; there is still a philosophical underpinning.  For instance, take Jeff Hawkins work at &lt;a href="http://www.numenta.com/"&gt;Numenta&lt;/a&gt;, which I have &lt;a href="http://paul-classic.stadig.name/articles/2007/3/23/jeff-hawkins-on-numenta"&gt;written about previously&lt;/a&gt;, clearly there are influences of &lt;a href="http://en.wikipedia.org/wiki/Empiricism"&gt;Empiricism&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We each have our own worldview, and our worldview not only influences the way we model the world, but it also limits our model of the world.  Programmers are fond of talking about how the programming language you use limits the way you think about solving a problem&lt;sup&gt;&lt;a href="#fn3"&gt;3&lt;/a&gt;&lt;/sup&gt;.  We encourage each other to learn multiple languages especially from different paradigms (declarative, functional, object oriented, imperative, etc.) to gain new ways of solving problems.  We have arguments about which languages model the world better.  Is action (functional languages) primary or is existence/state (object oriented) primary?  In other words which came first God existing, or God creating?  You could even liken the debate about static versus dynamic typing to a debate about absolute versus relative morality.  Can we possibly come up with a system of rules beforehand that are applied in every situation, or is that too rigid? ... OK maybe that last analogy is pushing it a bit...  :)&lt;/p&gt;

&lt;p&gt;I like what Rich has done.  He acknowledges that his philosophy of state and time has come from Alfred Whitehead.  Perhaps there are more lessons that can be learned from philosophy and applied to computer science.  Or perhaps if we are explicit about our philosophical underpinnings and follow them to their logical conclusions we will gain some useful insights.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Footnotes:&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;&lt;a name="fn1"&gt;1&lt;/a&gt;&lt;/sup&gt; This will most assuredly set some people afire, but I do not see a distinction between philosophy and religion.  They both answer questions we have about the nature of the universe, the limits and proper use of reasoning, etc.&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;&lt;a name="fn2"&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;a href="http://en.wikipedia.org/wiki/Pragmatism"&gt;Pragmatism&lt;/a&gt; is a branch of philosophy, by the way.&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;&lt;a name="fn3"&gt;3&lt;/a&gt;&lt;/sup&gt; Again from philosophy!  See &lt;a href="http://www.google.com/search?q=wittgenstein+philosophy+of+language"&gt;"Wittgenstein philosophy of language"&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/8086052983184217440-4013656518975832343?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/4013656518975832343/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=4013656518975832343" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/4013656518975832343?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/4013656518975832343?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/11/programming-of-philosophy.html" title="The Programming of Philosophy" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;CkAGR3Y8eSp7ImA9WxNVGU0.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-7608290257160310245</id><published>2009-10-30T06:35:00.002-04:00</published><updated>2009-10-30T07:05:26.871-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-30T07:05:26.871-04:00</app:edited><title>Simplicity and Complexity in Software</title><content type="html">&lt;p&gt;Avdi Grimm wrote &lt;a href="http://avdi.org/devblog/2009/10/29/simplicity-is-complicated/"&gt;"Simplicity is Complicated"&lt;/a&gt; where he argues that simplicity isn't simple.  It brought to mind a few pet peeves of my own.&lt;/p&gt;

&lt;p&gt;I think I agree with what Avdi has to say, but I might couch it in different language and come at it from the complexity side of the issue.  I also have some opinions on simplicity in programs, which basically boil down to readability.&lt;/p&gt;

&lt;p&gt;I like to think of complexity as being of two kinds: essential, and accidental.  Accidental complexity is complexity that is introduced by the way in which you solve the problem.  It is complexity that is unnecessary, and that could be removed by solving the problem in a more elegant fashion.  Essential complexity, however, is complexity that is inherent to the problem, and cannot be reduced no matter how you solve the problem.&lt;/p&gt;

&lt;p&gt;I had this conversation with a coworker recently.  We were working on several reports for a Rails application.  We were discussing whether it would be better to have a single controller with sixteen actions, or sixteen controllers with one action each.  This is an example of essential complexity.  If you have sixteen reports, then you are going to have sixteen "somethings," there is just no way around it.  This is like Avdi's example of a pocket of air trapped under plastic, if you squeeze on the actions, then sixteen controllers will pop up somewhere else.&lt;/p&gt;

&lt;p&gt;Then when it comes to simplicity in programs, my view is to reduce the accidental complexity as much as possible, and also to stick to the conventions and idioms of your language as much as possible.  Obviously, there would be no innovation if we only stuck to conventions, but as much as possible, a Ruby programmer should be able to read and quickly understand a Ruby program.  Consider these two examples from Avdi's article:&lt;/p&gt;

&lt;p&gt;Example 1
&lt;pre&gt;&lt;code&gt;
sum = 0
i = 0
while i &lt; times.length
  time = times[i]
  # parse / manipulate the time
  sum = sum + time
  i = i + 1
end
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Example 2
&lt;pre&gt;&lt;code&gt;
def average_time_of_day(times)
  sum = times.map(&amp;:to_time).inject(&amp;:+)
end
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;I don't think that Avdi is necessarily arguing for this viewpoint, but he says that "[Example 1] uses language constructs that are familiar to almost all programmers, not just Ruby programmers," and that this can be considered a form of simplicity.  While it's true that one might see that as a form of simplicity, I think the most important thing when writing a Ruby program should be writing it in a way that it is easy for Ruby programmers to understand.&lt;/p&gt;

&lt;p&gt;When a Ruby programmer sees Example 1, he has to stop and think about what is going on, because it is not idiomatic Ruby.  When he sees Example 2, he can immediately grasp the essence of what the code is doing.  This is more an issue of readability than simplicity.&lt;/p&gt;

&lt;p&gt;I'm not saying that the Ruby programmer grasps it easier because it is more terse.  A Java programmer would look at Example 2 and perhaps be a bit befuddled.  What I'm saying is that a Ruby programmer grasps it easily because it is idiomatic Ruby.  I do not think it would be appropriate to write something like Example 2 in Java.&lt;/p&gt;

&lt;p&gt;I would not advocate writing Java code with Ruby (like Example 1), nor would I advocate writing Ruby code with Java.  Stick to the idioms and conventions of the language you are working within.  And to those who say, "Well some Ruby programmers wouldn't easily grasp the essence of Example 2," I say, "You mean newbies?"  Tough.  They should master their tool.  They should read more code written by others.  That's part of being a professional programmer.&lt;/p&gt;

&lt;p&gt;So to summarize, the distinction between essential and accidental complexity is, I think, a useful and important one in thinking about complexity of code.  Second, to me the issue isn't so much simplicity as readability, and the key to readability is to stick to the idioms of your language as much as possible.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-7608290257160310245?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/7608290257160310245/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=7608290257160310245" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/7608290257160310245?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/7608290257160310245?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/10/simplicity-and-complexity-in-software.html" title="Simplicity and Complexity in Software" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;D08CSHczfCp7ImA9WxNVF0g.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-6786582477288280341</id><published>2009-10-28T13:46:00.002-04:00</published><updated>2009-10-28T14:51:09.984-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-28T14:51:09.984-04:00</app:edited><title>H1N1 "Swine" Flu</title><content type="html">&lt;p&gt;I have been confused about H1N1. The news reports that 1,000 people have died from it, but is that 1,000 out of 1,000 or 1,000 out of 17 million? There is no context to it. They report hyped up stories about a person dying from H1N1, but then it turns out the person did not die from H1N1 but from "complications"--where their immune system was compromised and they got an additional infection. You hear about how 60% of the hospitalizations and deaths are occurring in those under 65, when with the seasonal flu it is usually the reverse (i.e. 60% of the serious cases are in those over 65).&lt;/p&gt;

&lt;p&gt;Then if you're like us, you hear about H1N1 infections in friends and friends of friends, and they're not dying. You hear about school classrooms where 5-6 kids at a time contract it, yet there are no follow up reports about schools where half of the kids have died...apparently they are recovering from the infections without much fanfare. You hear about doctors telling their patients that they do not need to get confirmation that they have H1N1, because they would treat it the same either way. So much for tracking the "deadly" pandemic as it spreads like wildfire across the nation.&lt;/p&gt;

&lt;p&gt;Many health professionals are still seriously urging people to get vaccinated, but are saying that H1N1 is presenting in about the same way as the seasonal flu. Although there are some (perhaps disconcerting) differences from the seasonal flu, it is not some super deadly virus ravaging the earth's population.&lt;/p&gt;

&lt;p&gt;The US Federal Government's Flu.gov site has a &lt;a href="http://flu.gov/individualfamily/about/h1n1/index.html"&gt;page about H1N1&lt;/a&gt;. Here are the highlights (if you trust the government ;) ):&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;About 70% of the people who have been hospitalized have had one or more medical conditions that placed them in the "high risk" category.&lt;/li&gt;
&lt;li&gt;People over 64 do not appear to have an increased risk of complications.&lt;/li&gt;
&lt;li&gt;Although there have been hospitalizations and deaths, the vast majority of people who have contracted H1N1 have recovered without medical treatment.&lt;/li&gt;
&lt;li&gt;H1N1 spreads the same way as seasonal flu (coughing, sneezing, person-to-person contact). It is not an airborne super contagious version of the flu.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;The CDC also has a &lt;a href="http://www.cdc.gov/h1n1flu/surveillanceqa.htm"&gt;page about the characteristics of H1N1&lt;/a&gt;. More highlights:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;Between April and July of 2009, it is estimated that about 1 million people had been infected with H1N1, and of those 1 million about 5,000 people had been hospitalized and about 300 had died.&lt;/li&gt;
&lt;li&gt;H1N1 occurs most often among 5-24 year olds.&lt;/li&gt;
&lt;li&gt;Hospitalizations occur most often among 0-4 year olds.&lt;/li&gt;
&lt;li&gt;Deaths occur more often among 5-24 year olds. But again the deaths usually occur in cases where there are other underlying medical conditions.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Another whole set of issues, which I won't go into here, has to do with the vaccination.  All I can say is that as far as I know there is nothing "different" or "untested" about this vaccine.  It is prepared the same way as the vaccine for the seasonal flu, it just contains a different strain of the virus. There is more information about the vaccination at &lt;a href="http://flu.gov/individualfamily/vaccination/index.html"&gt;flu.gov&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I am not a health professional, statistician, or expert in any way.  I may have misinterpreted something, if so, let me know.  This is just food for thought, and perhaps a voice of balance among the hype.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-6786582477288280341?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/6786582477288280341/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=6786582477288280341" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/6786582477288280341?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/6786582477288280341?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/10/h1n1-swine-flu.html" title="H1N1 &quot;Swine&quot; Flu" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;DUINSH88eyp7ImA9WxNVFkg.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-2669787522903586446</id><published>2009-10-27T11:23:00.002-04:00</published><updated>2009-10-27T11:33:19.173-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-27T11:33:19.173-04:00</app:edited><title>Google Reader gets magic</title><content type="html">&lt;p&gt;Something I have long desired in a feed reader is magic.  Not just magic, but personalized magic.  I was all excited with Google Reader's "auto" sorting, which turned out to be less than useful.  The problem was that it was not personalized.&lt;/p&gt;

&lt;p&gt;I had even thought about creating a "smart" reader.  Something as simple as a naive bayes filter seemed like it would be a step in the right direction.  If I can teach a computer to recognize spam, then why can't I teach it to recognize the feed articles that I enjoy?  I had experimented with such a smart reader, but it was never enough of a problem for me to pursue it far enough.&lt;/p&gt;

&lt;p&gt;Enter Google Reader's new &lt;a href="http://googlereader.blogspot.com/2009/10/reading-gets-personal-with-popular.html"&gt;"magic"&lt;/a&gt; sorting.  Unlike the previous "auto" sorting, this one is personalized.  It takes into account the articles that I "like", "star" and "share."  I've been a big fan of &lt;i&gt;true&lt;/i&gt; personalization.  Sites like digg and reddit (and postrank in the blogosphere) are nice, but I don't want to read what the "community" finds interesting--which is often puerile 13 year old male content--I want to read what &lt;b&gt;I&lt;/b&gt; find interesting.&lt;/p&gt;

&lt;p&gt;Finally a feed reader seems to have what I'm looking for: true personalization.  I plan on using this feature, and I hope I won't be disappointed.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-2669787522903586446?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/2669787522903586446/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=2669787522903586446" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/2669787522903586446?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/2669787522903586446?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/10/google-reader-gets-magic.html" title="Google Reader gets magic" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;A0YDR3Y_fSp7ImA9WxJUEkg.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-4804483542735015087</id><published>2009-07-10T16:34:00.007-04:00</published><updated>2009-07-10T16:52:56.845-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-07-10T16:52:56.845-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="ruby" /><title>metric_fu, rcov, and rspec</title><content type="html">&lt;p&gt;I've been setting up &lt;a href="http://metric-fu.rubyforge.org/"&gt;metric_fu&lt;/a&gt; on one of my projects, and I ran into a problem where the rcov report was not including my specs.  (This is an old project that has both &lt;code&gt;Test::Unit&lt;/code&gt; tests and RSpec specs.)  I did lots and lots of digging, and was baffled that no one has mentioned that they cannot run coverage reports for their specs using metric_fu.  It seemed like an obvious and heinous oversight on the part of metric_fu.&lt;/p&gt;

&lt;p&gt;As always, I suspected that I was missing something.  &lt;code&gt;Test::Unit&lt;/code&gt; tests can be run individually from the command line like &lt;code&gt;ruby path/to/test&lt;/code&gt;, and metric_fu appeared to be trying to do this with the specs, but when I did &lt;code&gt;ruby path/to/spec&lt;/code&gt; I got nothing.&lt;/p&gt;

&lt;p&gt;I actually looked at the metric_fu specs to discover this, and it is probably very obvious to any serious RSpec user, but I discovered that I needed to add &lt;code&gt;require "spec/autorun"&lt;/code&gt; to the top of my &lt;code&gt;spec_helper.rb&lt;/code&gt; file.  This includes the magic that makes &lt;code&gt;ruby path/to/spec&lt;/code&gt; work, and makes rcov include my specs in its report when it is invoked by metric_fu.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-4804483542735015087?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/4804483542735015087/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=4804483542735015087" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/4804483542735015087?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/4804483542735015087?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/07/metricfu-rcov-and-rspec.html" title="metric_fu, rcov, and rspec" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>3</thr:total></entry><entry gd:etag="W/&quot;DUMEQ306eSp7ImA9WxVaF0k.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-10255694715628837</id><published>2009-04-14T16:35:00.006-04:00</published><updated>2009-04-14T18:16:42.311-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-14T18:16:42.311-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="terracotta" /><category scheme="http://www.blogger.com/atom/ns#" term="clojure" /><title>Terracotta Bug Reports</title><content type="html">&lt;p&gt;The integration work with Clojure and Terracotta has been progressing.  After I brought up a couple of the issues I had encountered on the tc-dev mailing list, it turned out that I had discovered a couple of bugs in Terracotta.&lt;/p&gt;

&lt;p&gt;These three issues were the source of some of the major changes that were required to integrate Clojure and Terracotta, and they should all be fixed in Terracotta 3.0.1:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jira.terracotta.org/jira/browse/CDV-1171"&gt;CDV-1171: Support java.util.concurrent.atomic.AtomicReference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jira.terracotta.org/jira/browse/CDV-1233"&gt;CDV-1233: identity comparsion (reference equality) of DSO literals does not work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jira.terracotta.org/jira/browse/CDV-1234"&gt;CDV-1234: clone() on shared array types might fail to resolve values before native cloning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;In particular, CDV-1233 required some ugly changes in the compiler, which should now be (thankfully!) unnecessary.&lt;/p&gt;

&lt;p&gt;I believe I also have a solution to the problem that comes about when a root Var binding is a non-portable object.  Stay tuned for that!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-10255694715628837?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/10255694715628837/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=10255694715628837" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/10255694715628837?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/10255694715628837?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/04/terracotta-bug-reports.html" title="Terracotta Bug Reports" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DUYFQH86eSp7ImA9WxVbFEk.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-5199015618782459825</id><published>2009-03-30T15:40:00.006-04:00</published><updated>2009-03-30T17:05:11.111-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-30T17:05:11.111-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="terracotta" /><category scheme="http://www.blogger.com/atom/ns#" term="clojure" /><title>Clojure + Terracotta Update</title><content type="html">&lt;p&gt;I have gotten to the point in my Clojure + Terracotta experiment, where I believe all of the features of Clojure are functional (Refs, Atoms, transactions, etc.).  I do not have a way to extensively test the Clojure functionality, but I have run the &lt;code&gt;clojure.contrib.test-clojure&lt;/code&gt; test suites successfully, as well as some simple tests on my machine.&lt;/p&gt;

&lt;p&gt;There are still some open issues, and given the limited extent to which I have tested this, I would not consider this production quality in the least.  I would welcome help from the Clojure community in testing this integration module.  I'm sure there are unexplored corners.&lt;/p&gt;

&lt;p&gt;Being that several of the changes are relatively trivial, they could be easily integrated back into the Clojure core.  I have detailed as best as possible the changes I had to make to Clojure in this report: &lt;a href="http://docs.google.com/Doc?id=dg7c7v49_241g5t8tqsv"&gt;Clojure + Terracotta Integration Report&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code is available at GitHub (&lt;a href="http://github.com/pjstadig/tim-clojure-1.0-snapshot/tree/master"&gt;http://github.com/pjstadig/tim-clojure-1.0-snapshot/tree/master&lt;/a&gt;), and there are instructions on setting it up and running the code.  If you have any difficulties or questions, please feel free to e-mail me &lt;a href="mailto:paul@stadig.name?subject=Clojure%20+%20Terracotta%20Question"&gt;paul@stadig.name&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/8086052983184217440-5199015618782459825?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/5199015618782459825/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=5199015618782459825" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5199015618782459825?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5199015618782459825?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/03/clojure-terracotta-update.html" title="Clojure + Terracotta Update" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DUUER3o7eyp7ImA9WxVbFEk.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-5619961015242967220</id><published>2009-03-05T15:00:00.010-05:00</published><updated>2009-03-30T17:06:46.403-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-30T17:06:46.403-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="terracotta" /><category scheme="http://www.blogger.com/atom/ns#" term="clojure" /><title>Clojure + Terracotta: We Have REPLs!</title><content type="html">&lt;p style="background-color: #eeeeee; padding: 3px; border: 2px solid black"&gt;&lt;strong&gt;Update:&lt;/strong&gt; The Clojure + Terracotta integration is (I believe) feature complete.  Details at &lt;a href="http://paul.stadig.name/2009/03/clojure-terracotta-update.html"&gt;http://paul.stadig.name/2009/03/clojure-terracotta-update.html&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;JVM #1&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;paul@pstadig-laptop:~/tim-clojure/tim-clojure-1.0-SNAPSHOT/examples/shared-everything$ ./bin/dso-clojure repl.clj 
Starting BootJarTool...
2009-03-05 15:00:10,868 INFO - Terracotta 2.7.3, as of 20090129-100125 (Revision 11424 by cruise@su10mo5 from 2.7)
2009-03-05 15:00:11,428 INFO - Configuration loaded from the file at '/home/paul/tim-clojure/tim-clojure-1.0-SNAPSHOT/examples/shared-everything/tc-config.xml'.

Starting Terracotta client...
2009-03-05 15:00:14,904 INFO - Terracotta 2.7.3, as of 20090129-100125 (Revision 11424 by cruise@su10mo5 from 2.7)
2009-03-05 15:00:15,436 INFO - Configuration loaded from the file at '/home/paul/tim-clojure/tim-clojure-1.0-SNAPSHOT/examples/shared-everything/tc-config.xml'.
2009-03-05 15:00:15,656 INFO - Log file: '/home/paul/terracotta/client-logs/org.terracotta.modules.sample/20090305150015636/terracotta-client.log'.
2009-03-05 15:00:17,870 INFO - Statistics buffer: '/home/paul/tim-clojure/tim-clojure-1.0-SNAPSHOT/examples/shared-everything/statistics-127.0.1.1'.
2009-03-05 15:00:18,421 INFO - Connection successfully established to server at 127.0.1.1:9510
user=&gt; (defn foo [] 42)
#'user/foo
user=&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;JVM #2&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;paul@pstadig-laptop:~/tim-clojure/tim-clojure-1.0-SNAPSHOT/examples/shared-everything$ ./bin/dso-clojure repl.clj
Starting BootJarTool...
2009-03-05 15:01:39,663 INFO - Terracotta 2.7.3, as of 20090129-100125 (Revision 11424 by cruise@su10mo5 from 2.7)
2009-03-05 15:01:40,225 INFO - Configuration loaded from the file at '/home/paul/tim-clojure/tim-clojure-1.0-SNAPSHOT/examples/shared-everything/tc-config.xml'.

Starting Terracotta client...
2009-03-05 15:01:45,507 INFO - Terracotta 2.7.3, as of 20090129-100125 (Revision 11424 by cruise@su10mo5 from 2.7)
2009-03-05 15:01:46,091 INFO - Configuration loaded from the file at '/home/paul/tim-clojure/tim-clojure-1.0-SNAPSHOT/examples/shared-everything/tc-config.xml'.
2009-03-05 15:01:46,275 INFO - Log file: '/home/paul/terracotta/client-logs/org.terracotta.modules.sample/20090305150146254/terracotta-client.log'.
2009-03-05 15:01:50,868 INFO - Connection successfully established to server at 127.0.1.1:9510
"user=&gt; "(foo)
42
"user=&gt; "*print-dup*
false
"user=&gt; "
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Commentary&lt;/h3&gt;
&lt;p&gt;This is obviously an example of the "&lt;a href="http://paul.stadig.name/2009/03/clojure-terracotta-next-steps.html"&gt;shared everything&lt;/a&gt;" approach.  It's neither perfect nor complete, but it's a start.  There are still some non-portable classes that need to be reworked, for some reason the second VM is printing the REPL prompt readably even though &lt;code&gt;*print-dup*&lt;/code&gt; (as you can see) is false, I still haven't worked out the problem with &lt;code&gt;*in*&lt;/code&gt;, &lt;code&gt;*out*&lt;/code&gt;, and &lt;code&gt;*err*&lt;/code&gt;, etc. etc.&lt;/p&gt;

&lt;p&gt;It's still very raw, but I'll see if I can't push to github in the next day or two.  This is an exciting first step!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-5619961015242967220?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/5619961015242967220/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=5619961015242967220" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5619961015242967220?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5619961015242967220?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/03/clojure-terracotta-we-have-repl.html" title="Clojure + Terracotta: We Have REPLs!" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;CkQNRX85fip7ImA9WxVVEkQ.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-5979445697363880716</id><published>2009-03-03T17:17:00.011-05:00</published><updated>2009-03-05T16:59:54.126-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-05T16:59:54.126-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="terracotta" /><category scheme="http://www.blogger.com/atom/ns#" term="clojure" /><title>Clojure + Terracotta: The Next Steps</title><content type="html">&lt;p style="background-color: #eeeeee; padding: 3px; border: 2px solid black"&gt;&lt;strong&gt;Update:&lt;/strong&gt; I've gotten a *multiple* REPLs running with Terracotta. &lt;a href="http://paul.stadig.name/2009/03/clojure-terracotta-we-have-repl.html"&gt;http://paul.stadig.name/2009/03/clojure-terracotta-we-have-repl.html&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In my &lt;a href="http://paul.stadig.name/2009/02/clojure-terracotta-yeah-baby.html"&gt;last post&lt;/a&gt; about Clojure + Terracotta I gave an example of sharing specific references between JVMs through Terracotta.  This is what I call the "shared somethings" approach.  You specify exactly what you like to share.  Another approach is what I call the "shared everything" approach.&lt;/p&gt;

&lt;h3&gt;Shared Everything&lt;/h3&gt;
&lt;p&gt;The goal of shared everything is to have multiple VMs working within a single global context through Terracotta, all of your vars and refs would be shared by default, and the canonical test case for this would be to define a function in one VM and have it show up automatically in another VM.&lt;/p&gt;

&lt;p&gt;The first task was to move my work into a Terracotta Integration Module (TIM).  When using a TIM, in addition to packaging the configuration for reuse, classes can be replaced with clustered versions that will work with Terracotta, without having to fork the original code base.&lt;/p&gt;

&lt;p&gt;The second was to replace a couple of classes in the Clojure codebase.  The &lt;code&gt;Namespace&lt;/code&gt; class uses &lt;code&gt;AtomicReference&lt;/code&gt;, which is not supported by Terracotta.  There was a minor change necessary in &lt;code&gt;Var&lt;/code&gt;, too, because it was using its &lt;code&gt;dvals&lt;/code&gt; field as a sentinel value to indicate that the var is not bound.  This does not do for Terracotta, because &lt;code&gt;dvals&lt;/code&gt; is a &lt;code&gt;ThreadLocal&lt;/code&gt;, so I created a &lt;code&gt;NOT_BOUND&lt;/code&gt; sentinel field.  There were some other changes as well, I'm not going to detail all of the changes, but you get the idea.&lt;/p&gt;

&lt;p&gt;At this point I would have hoped that I could run a REPL and possibly even try my canonical test case, but it should be so easy.  I have run into two major roadblocks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;*in*&lt;/code&gt;, &lt;code&gt;*out*&lt;/code&gt;, and &lt;code&gt;*err*&lt;/code&gt;.&lt;/strong&gt; Being I/O streams, &lt;code&gt;*in*&lt;/code&gt;, &lt;code&gt;*out*&lt;/code&gt;, and &lt;code&gt;*err*&lt;/code&gt; obviously cannot be shared through Terracotta.  The problem is that they are stored as Vars and interned into the &lt;code&gt;clojure.core&lt;/code&gt; namespace.  This means that Terracotta will try to share them, because they are part of the shared object graph.  I could make &lt;code&gt;clojure.lang.Var.root&lt;/code&gt; a transient field (through Terracotta's configuration file), but that would make the roots of all Vars transient, which is not what we want.  Instead, what I thought I needed is some kind of &lt;code&gt;TransientVar&lt;/code&gt; that could have a different root value (not just bindings) for each JVM.  I pursued this a bit using the class replacement of the TIM, and concluded that if that is the route to go, then it should probably be made in the Clojure code (it got messy), or that at least there are some changes to the Clojure code that would ease this.  What I settled on (after a suggestion from Rich) is to leave the root bindings for *in*, *out*, and *err* as nil and allow the REPL to bind them.  However, the REPL did not bind them for me, so I created my own &lt;code&gt;repl.clj&lt;/code&gt; file that binds them and calls &lt;code&gt;clojure.main/repl&lt;/code&gt;, and it works!  However, this is only a temporary solution.  Whether it is creating a TransientVar class, or something else, we need a more permanent solution.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Classes.&lt;/strong&gt;  I am able to connect a single JVM to Terracotta, and run the REPL, but I cannot connect multiple JVMs, nor can I disconnect and reconnect a single JVM.  When an instance of Clojure connects to Terracotta, it pulls a compiled function out of the object cache, and then throws a &lt;code&gt;ClassNotFoundException&lt;/code&gt; because it cannot find the associated class.  I started to pursue modifying the &lt;code&gt;DynamicClassLoader&lt;/code&gt; and &lt;code&gt;Compiler&lt;/code&gt; to store the compiled classes in the Terracotta object graph, and I still think that this might be the direction to go in.  However, I wanted to go ahead and share what I have and get some feedback to see if there are any other solutions.&lt;/li&gt;&lt;/ol&gt;

&lt;p&gt;The code is available at &lt;a href="http://github.com/pjstadig/tim-clojure-1.0-snapshot/tree/master"&gt;http://github.com/pjstadig/tim-clojure-1.0-snapshot/tree/master&lt;/a&gt;.  In the "examples" directory I have a "shared-everything" example and a "shared-somethings" example.  If you have any trouble running the examples, then let me know.  There are some dead ends and some commented code that may not make sense, but my goal was to do a proof-of-concept first and to clean it up once I understand what needs to be done.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;We are getting close to a "shared-everything" approach to integrating Clojure and Terracotta.  We have some issues to deal with, but we are on our way to making this dream a reality.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-5979445697363880716?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/5979445697363880716/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=5979445697363880716" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5979445697363880716?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5979445697363880716?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/03/clojure-terracotta-next-steps.html" title="Clojure + Terracotta: The Next Steps" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;DkECSXg_cSp7ImA9WxVVEU8.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-5566731897437362758</id><published>2009-02-27T15:00:00.004-05:00</published><updated>2009-03-03T18:57:48.649-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-03T18:57:48.649-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="terracotta" /><category scheme="http://www.blogger.com/atom/ns#" term="clojure" /><title>Clojure + Terracotta = Yeah, Baby!</title><content type="html">&lt;p style="background-color: #eeeeee; padding: 3px; border: 2px solid black"&gt;&lt;strong&gt;Update:&lt;/strong&gt; I've gotten a REPL running with Terracotta. &lt;a href="http://paul.stadig.name/2009/03/clojure-terracotta-next-steps.html"&gt;http://paul.stadig.name/2009/03/clojure-terracotta-next-steps.html&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;What is Terracotta?&lt;/h3&gt;
&lt;p&gt;Terracotta &lt;a href="http://orionl.blogspot.com/2008/03/terracotta-is-not-object-cache.html"&gt;provides&lt;/a&gt; a network-attached, virtual, persistent heap and transparent inter-JVM thread coordination.  With Terracotta, you no longer need to map your objects to database tables and back.  You simply hand your object to Terracotta and it will cache your data.  Not only does it cache your data, but it will make your object available to a cluster of networked JVMs.  Not only that, but it will also spill your objects to disk if necessary (just like Virtual Memory), so you need not worry about having gobs of memory to hold all of your objects.&lt;/p&gt;

&lt;h3&gt;What is Clojure?&lt;/h3&gt;
&lt;p&gt;Clojure is a Lisp for the JVM with a software transactional memory, and agents (asynchronous, message based concurrency).  It is a functional language with immutable datatypes.  It can also inter-operate with any existing Java code.&lt;/p&gt;

&lt;p&gt;NOTE: you need to use Clojure &lt;a href="http://code.google.com/p/clojure/source/browse/trunk/src/jvm/clojure/lang/Keyword.java?r=1310"&gt;r1310&lt;/a&gt; or later, because the Keyword class needs to have hashCode implemented to play nicely with Terracotta.&lt;/p&gt;

&lt;h3&gt;Clojure + Terracotta = ?&lt;/h3&gt;
&lt;p&gt;These two seem like an interesting combination.  Imagine the possibilities...kill your database, simple POJO applications, free distributed transactions, clustered JVMs with limitless memory...it would make your hair would grow back, you'd get women, and become filthy rich...well...maybe not, but at least you'd have more fun writing software.&lt;/p&gt;

&lt;p&gt;After some initial setup (the code and instructions are at &lt;a href="http://github.com/pjstadig/terraclojure/tree/master/"&gt;http://github.com/pjstadig/terraclojure/tree/master/&lt;/a&gt;), there are two things that need to be done to integrate Clojure and Terracotta: 1) instead of running Clojure with the &lt;code&gt;'java'&lt;/code&gt; command, you run it with the &lt;code&gt;'dso-java.{sh,bat}'&lt;/code&gt; script provided with Terracotta, and 2) you need to create a configuration file that defines how your objects will be shared between JVMs.&lt;/p&gt;

&lt;h3&gt;Configuration&lt;/h3&gt;
&lt;p&gt;The configuration for Terracotta (at least in our case) consists of defining: roots, instrumented classes, auto-locks, additional boot jar classes, and servers.  At this point it's probably helpful to take a peek at the &lt;code&gt;config.xml&lt;/code&gt; file that comes with the code and follow along.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Roots.&lt;/strong&gt; A root is a object that is shared between JVMs.  Any objects that are part of the object graph that can be reached from the root are also shared, so any objects that are assigned to data members, etc.  A common use case is to have a &lt;code&gt;ConcurrentHashMap&lt;/code&gt; (or in our case a &lt;code&gt;PersistentHashMap&lt;/code&gt; from Clojure) that is shared as a root.  This creates a flexible hierarchy of shared objects.  In Clojure's case, we also share &lt;code&gt;clojure.lang.Keyword.table&lt;/code&gt;, so that our keywords are unique across all of the JVMs, otherwise inserting into a hash map would create multiple entries for the same keyword.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Instrumented classes.&lt;/strong&gt; Any class that is shared (either directly as a root, or indirectly as a part of a root's object graph), must be instrumented.  I made all of the &lt;code&gt;clojure.lang.*&lt;/code&gt; classes instrumented.  It's a bit of a broad stroke, but there aren't any performance problems that result from instrumenting too many classes.  Terracotta is helpful in this case, if you end up inserting an uninstrumented class into the object graph, it'll throw a &lt;code&gt;RuntimeException&lt;/code&gt; that explains exactly how to modify your config file to instrument that class.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Auto-locks.&lt;/strong&gt; Terracotta will transparently convert your synchronized blocks into distributed transactions across all the JVMs in the cluster.  Again, I made broad strokes here and just defined auto-locks for all of the methods on any &lt;code&gt;clojure.lang.*&lt;/code&gt; class, and again, there aren't any performance penalties for auto-locking methods that don't have any synchronized blocks.  I used write locks, and Terracotta has a few &lt;a href="http://terracotta.org/web/display/docs/Configuration+Guide+and+Reference#ConfigurationGuideandReference-/tc:tcconfig/application/dso/locks"&gt;different types&lt;/a&gt; of locks that are worth looking into if you need to do something more serious.  In the case of auto-locks, Terracotta will also help you out by throwing a &lt;code&gt;RuntimeException&lt;/code&gt; if you leave out anything.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Additional boot jar classes.&lt;/strong&gt; Frankly, this was something Terracotta told me to do, and I don't know exactly what is going on here.  (Perhaps someone else can explain?)  I think what happens is that by default Terracotta instruments the &lt;code&gt;java.lang.*&lt;/code&gt; and &lt;code&gt;java.util.concurrent.*&lt;/code&gt; classes, but to instrument other Java core classes you have to add them in this configuration element.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Servers.&lt;/strong&gt; Terracotta is very easy to work with, and by default will just run a single server on &lt;code&gt;localhost&lt;/code&gt;.  You can define more than one server in a cluster.  In my case, I only wanted one server, but I wanted to change the persistence mode.  By default the persistence mode is a &lt;code&gt;temporary-swap-only&lt;/code&gt; mode.  The objects will be preserved across stopping and starting clients, but once the server is stopped, the data disappears. To have the objects persisted across restarting the server, you have to set the persistence mode to &lt;code&gt;permanent-store&lt;/code&gt;.  The temporary swap mode will be faster for data like the intermediate results of calculations, caching, etc., but if you need to permanently persist the data, then you need to use &lt;code&gt;permament-store.&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are instructions about how to run this example in the README with the code, so I won't bother to duplicate that here.  I'd just like to share some of the issues I encountered, the results, and any future direction that could be taken.&lt;/p&gt;

&lt;h3&gt;Issues&lt;/h3&gt;
&lt;p&gt;The first major issue that I encountered was that Keywords weren't unique across JVMs, so I had to make &lt;code&gt;clojure.lang.Keyword.table&lt;/code&gt; a root.  This ensured that keywords are unique across JVMs, but I still ran into an issue when using keywords as keys for a &lt;code&gt;PersistentHashMap&lt;/code&gt;.  The result of &lt;code&gt;identical?&lt;/code&gt; was &lt;code&gt;true&lt;/code&gt; for keywords from two JVMs, but I was still getting duplicate entries in my hash map.  After some debugging, I was able to determine that the issue is that the keyword class did not override the default implementation of &lt;code&gt;hashCode&lt;/code&gt;.  After mentioning this to Rich, and a quick fix in r1310, it worked nicely.&lt;/p&gt;

&lt;p&gt;The only other major issue was how to reference Clojure vars and refs from the Java side.  The main reason for this is to define a root that will be shared by Terracotta.  When Clojure code gets compiled some Java classes get generated with mangled names.  As far as I can tell, there isn't a good predictable way to get at a Clojure var, because Clojure will generate a class for each namespace called &lt;code&gt;my/namespace/namespace__init.class&lt;/code&gt; and it creates static fields on that class for various definitions (functions, vars, etc.).  Those fields are named &lt;code&gt;const_1&lt;/code&gt;, &lt;code&gt;const_2&lt;/code&gt;, &lt;code&gt;const_3&lt;/code&gt;, etc.  There is no reliable, flexible way to predict the name of a particular &lt;code&gt;Var&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;My solution was to create a simple Java class called &lt;code&gt;terraclojure.Root&lt;/code&gt; with a couple of static fields containing refs.  At first I just used that class directly to access the refs, but then I decided to actually assign the static fields to some vars in my namespace, i.e. &lt;code&gt;(def *hash* terraclojure.Root/hash)&lt;/code&gt;.  This works and it makes it a little more transparent on the Clojure side.  I would be happy to hear if there is another way to do this.&lt;/p&gt;

&lt;h3&gt;Result&lt;/h3&gt;
&lt;p&gt;The result of this whole experiment was that I am able to use the Software Transactional Memory with a couple of refs, and to have my changes shared across multiple JVMs.  I didn't do any extensive testing to verify that transaction retries work as expected, but since Clojure uses the &lt;code&gt;java.util.concurrent.*&lt;/code&gt; classes and standard synchronization, I don't expect there would be an issues.&lt;/p&gt;

&lt;h3&gt;Where do we go from here?&lt;/h3&gt;
&lt;p&gt;I only experimented with the STM.  I didn't experiment with Agents, so that is certainly an area for future work.  On the Terracotta side, I only used one server, I didn't setup a whole array of servers, nor did I try using one or more servers on different machines.  All my testing was local, so the performance reflected that (it was pretty good! :)).  If you do any further experimentation, then please share it on a blog or to the &lt;a href="http://groups.google.com/group/clojure"&gt;Google group&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I don't have a lot of experience with Terracotta, but it seems to be quite mature and easy-to-use.  I also think that Clojure is a very exciting language, and the combination of the two opens up some interesting possibilities for how to architect highly available, scalable, database-less applications.&lt;/p&gt;

&lt;p&gt;P.S. I have a B.S. in Computer Science and will have an M.S. in Computer Science in May.  I don't do anything near this interesting at my job.  If you have any need for consulting, or if you'd like to offer me a job ;), then feel free to contact me at &lt;a href="mailto:paul@stadig.name"&gt;paul@stadig.name&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/8086052983184217440-5566731897437362758?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/5566731897437362758/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=5566731897437362758" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5566731897437362758?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5566731897437362758?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/02/clojure-terracotta-yeah-baby.html" title="Clojure + Terracotta = Yeah, Baby!" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;A08FR389eyp7ImA9WxVbFk8.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-5343912529192778313</id><published>2009-02-20T12:26:00.006-05:00</published><updated>2009-04-01T19:50:16.163-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-01T19:50:16.163-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><title>Rails, respond_to, IE6, and the Accept Header</title><content type="html">&lt;p&gt;Pain...much pain caused by IE6.&lt;/p&gt;

&lt;p&gt;If you've worked with respond_to in Rails, you know what a cool idea it is.  Provide access to the same resource in different formats based on either the extension on the URL (i.e. http://something/people/1.xml), or based on an HTTP header that your browser send to the web server, called the Accept header.&lt;/p&gt;

&lt;p&gt;It sounds good, but in practice there is one particular browser (*cough* IE) that causes problems.  I got into it thinking, "I don't need to worry about this 'Accept' header thing.  If a user pulls up http://something/people/1 they'll get an HTML version and if they pull up http://something/people/1.xml they'll get an XML version."  This fallacious (?!) reasoning works like a champ with Firefox and IE7 (I think it's getting hazy at this point), but IE6 FAIL!&lt;/p&gt;

&lt;p&gt;&lt;a href="http://blog.peelmeagrape.net/2007/8/15/respond_to-order-is-important"&gt;Change the order&lt;/a&gt; of my respond_to block? FAIL!  How about &lt;a href="http://rituonrails.wordpress.com/2006/12/10/strane-behaviour-of-respond_to-in-ie/#comment-41"&gt;forcing a sane&lt;/a&gt; Accept header?  Sweet! It works, until I upgrade rails and now the request headers are frozen.  FAIL! (This may have been my problem because I wasn't doing it right, but it doesn't matter, there is a better way.)  How about just explicitly specifying the :format for every URL in the application?  Annoying, tedious, but it works, until I get a call from a user, "When I search here and click there I get a 'data dump.'"  FAIL!&lt;/p&gt;

&lt;p&gt;At this point, I may be doing something wrong.  Perhaps one of the above solutions "should" have worked, but I'm mad...there has to be a better way.  Can't Rails just serve HTML by default and some other format when you specify the extension?  Can't Rails just ignore the Accept header?  It turns out that there was a &lt;a href="http://github.com/rails/rails/commit/2f4aaed7b3feb3be787a316fab3144c06bb21a27"&gt;commit&lt;/a&gt; on June 27, 2008 that did just that.  This was supposedly done for Rails 2.2, and I'm running 2.2.2, so why am I not benefiting from it?  Because two weeks later it was &lt;a href="http://github.com/rails/rails/commit/4ce9931f4f30045b2975328e7d42a02188e35079"&gt;undone&lt;/a&gt;.  However, we're on the right track now.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://rituonrails.wordpress.com/2006/12/10/strane-behaviour-of-respond_to-in-ie/"&gt;Given&lt;/a&gt; &lt;a href="http://info.michael-simons.eu/2007/08/06/rails-respond_to-method/"&gt;that&lt;/a&gt; &lt;a href="http://blog.wejoinin.com/2007/10/10/learning-an-important-lesson/"&gt;this&lt;/a&gt; &lt;a href="http://garrickvanburen.com/archive/workaround-for-ie-overly-accepting-in-rails-respond_to-format"&gt;is&lt;/a&gt; &lt;a href="http://groups.google.com/group/orug-discussion/browse_thread/thread/273ecade348545de"&gt;such&lt;/a&gt; a widely known issue, I don't know why someone hasn't posted the magic solution until now, but here it is...Are you ready?  Add this line to config/environments/{test,development,production}.rb:&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;
config.action_controller.use_accept_header = false
&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;There...that was simple.  You're welcome.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-5343912529192778313?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/5343912529192778313/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=5343912529192778313" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5343912529192778313?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5343912529192778313?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/02/rails-respondto-ie6-and-accept-header.html" title="Rails, respond_to, IE6, and the Accept Header" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>4</thr:total></entry><entry gd:etag="W/&quot;DEQCR305eyp7ImA9WxVXFU8.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-6486580900134095948</id><published>2009-02-13T06:37:00.003-05:00</published><updated>2009-02-13T06:59:26.323-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-13T06:59:26.323-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="ruby" /><title>To 'and' or not to 'and'</title><content type="html">&lt;p&gt;Ruby has two 'or' operators ('||' and 'or').  It also has two 'and' operators ('&amp;&amp;' and 'and').  This can be confusing to people, but especially to those learning the language.  There is a temptation to use 'and' and 'or' because it is more readable, and I can certainly appreciate that.  However, there are some serious differences between these operators, and I recommend only using '&amp;&amp;' and '||' in boolean expressions.&lt;/p&gt;

&lt;p&gt;Of the two, 'and' has lower precedence than '&amp;&amp;', and it is the same with 'or' and '||'.  This means that there is a difference between:&lt;/p&gt;

&lt;pre&gt;irb(main):001:0&amp;gt; true || false &amp;amp;&amp;amp; false
=&amp;gt; true
&lt;/pre&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;pre&gt;irb(main):002:0&amp;gt; false || true and false
=&amp;gt; false
&lt;/pre&gt;

&lt;p&gt;You might then be tempted to just adopt the practice of always using 'or' and 'and', but that also might surprise you:&lt;/p&gt;

&lt;pre&gt;irb(main):023:0&amp;gt; true or false and false
=&amp;gt; false
&lt;/pre&gt;

&lt;p&gt;This surprising result follows from the fact that, whereas '&amp;&amp;' has a higher precedence than '||', 'or' has the same precedence as 'and', so Ruby just evaluates the statement left to right handling first the 'or' then the 'and'.&lt;/p&gt;

&lt;p&gt;Even though you may understand the nuances between these operators, not everyone may understand, and the fact is that 99% of programmers in the world (really 100% I would hope) can understand statements involving '&amp;&amp;' and '||'.  So let's just stick with the traditional boolean operators, because in the end it is actually more readable.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-6486580900134095948?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/6486580900134095948/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=6486580900134095948" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/6486580900134095948?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/6486580900134095948?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/02/to-and-or-not-to-and.html" title="To 'and' or not to 'and'" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DEQMQ3k7eSp7ImA9WxVXFU8.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-3810868683580294043</id><published>2009-02-06T09:30:00.010-05:00</published><updated>2009-02-13T06:59:42.701-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-13T06:59:42.701-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="css" /><title>CSS: multiple class selection</title><content type="html">&lt;p&gt;I don't know how many times I've wished that I could select an HTML element that has two classes.  I want to select the table rows that are both 'odd' and 'awesome'.  So instead of doing this:&lt;/p&gt;

&lt;pre&gt;
...
&amp;lt;tr class="even awesome"&amp;gt;...&amp;lt;/tr&amp;gt;
&amp;lt;tr class="odd awesome"&amp;gt;...&amp;lt;/tr&amp;gt;
...
&lt;/pre&gt;

&lt;p&gt;I end up doing this:&lt;/p&gt;

&lt;pre&gt;
...
&amp;lt;tr class="even awesome even_awesome"&amp;gt;...&amp;lt;/tr&amp;gt;
&amp;lt;tr class="odd awesome odd_awesome"&amp;gt;...&amp;lt;/tr&amp;gt;
...
&lt;/pre&gt;

&lt;p&gt;I always felt dirty doing something like that, and thought there had to be a better way to do it.  Well there is!  It turns out that '.odd.awesome' will select those elements with both the 'odd' and 'awesome' classes.&lt;/p&gt;

&lt;p&gt;This stylesheet:&lt;/p&gt;

&lt;pre&gt;
.odd_awesome {
  color: red;
  size: 48pt;
}
&lt;/pre&gt;

&lt;p&gt;Has now become:&lt;/p&gt;

&lt;pre&gt;
.odd.awesome {
  color: red;
  size: 48pt;
}
&lt;/pre&gt;

&lt;p&gt;And the HTML is simply:&lt;/p&gt;

&lt;pre&gt;
...
&amp;lt;tr class="even awesome"&amp;gt;...&amp;lt;/tr&amp;gt;
&amp;lt;tr class="odd awesome"&amp;gt;...&amp;lt;/tr&amp;gt;
...
&lt;/pre&gt;

&lt;p&gt;Now that I know this secret, I vaguely remember having known it many years ago (like when I was first introduced to CSS), but somehow I had forgotten it.  It's like running into an old friend.  "Hello Mr. CSS Selector!  It's been a long time."&lt;/p&gt;

&lt;p&gt;Now go simplify your HTML/CSS!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-3810868683580294043?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/3810868683580294043/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=3810868683580294043" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/3810868683580294043?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/3810868683580294043?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2009/02/css-multiple-class-selection.html" title="CSS: multiple class selection" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;AkQGRnk-fyp7ImA9WxRaFkU.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-2456632738553392190</id><published>2008-12-19T06:09:00.009-05:00</published><updated>2008-12-19T07:05:27.757-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-19T07:05:27.757-05:00</app:edited><title>Redeem the Time</title><content type="html">&lt;p&gt;As we close the year 2008 and come near to that time of New Year's Resolutions, here is an idea for you.  If you are as unlucky as myself to have to commute every day to work, then redeem the time.  I have at various times while driving to/from work listened to: sermons, lectures on philosophy, home brewing podcasts, audio books, plays, TV shows, and more.  It is much more interesting than listening to the radio, and it's possible with a $30 MP3 player.&lt;/p&gt;

&lt;p&gt;"How do you find great sources?" you may ask.  Here are some tips:
&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;My &lt;a href="http://www.fairfaxcounty.gov/library/dbsremote/resource/audiobooks.htm"&gt;county library system&lt;/a&gt; has an online audiobook library.  I can checkout and download audiobooks for free.  There are WMA books, but surprisingly there are also some MP3 books!&lt;/li&gt;
&lt;li&gt;Check out Project Guetenberg, in addition to the computer read books they also have &lt;a href="http://www.gutenberg.org/browse/categories/1"&gt;human read books&lt;/a&gt;, and some of them are excellent quality.  In particular I can recommend the renditions of Sherlock Holmes, they are absolutely wonderful...and free!&lt;/li&gt;
&lt;li&gt;Find some podcasts you like.  There are excellent podcasts on all sorts of topics.  I have picked up podcasts on &lt;a href="http://feeds.feedburner.com/rubyonrailspodcast/"&gt;Ruby&lt;/a&gt;, &lt;a href="http://www.basicbrewing.com/radio/radio.rss"&gt;home brewing&lt;/a&gt;, &lt;a href="http://leoville.tv/podcasts/twit.xml"&gt;technology news&lt;/a&gt;, &lt;a href="http://www.cbc.ca/podcasting/includes/quirks.xml"&gt;science&lt;/a&gt;, &lt;a href="http://marketplace.publicradio.org/podcast/podcast.php?show_id=14"&gt;business news&lt;/a&gt;, and more.  There are also some TV shows like &lt;i&gt;60 Minutes&lt;/i&gt; that have a &lt;a href="http://feeds.cbsnews.com/podcast_60min_1?format=xml"&gt;podcast replay&lt;/a&gt; of the entire show commercial free.  A site I like for managing podcast subscriptions is &lt;a href="http://podnova.com/"&gt;PodNova&lt;/a&gt;.  They have a downloadable aggregator that works on Linux.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;So why not listen to some of those books you always wanted to read?  Jane Austen, F. Scott Fitzgerald, J.R.R. Tolkien, Lewis Carroll, Jules Verne, Charles Dickens, Mark Twain, Fyodor Dostoevsky, Voltaire...they're all here.  Listen to &lt;a href="http://www.audiotreasure.com/indexKJV.htm"&gt;the Bible&lt;/a&gt; read to you.  Fiction or Non-fiction, be creative, learn a new language, or a new subject.  It's easy and free.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-2456632738553392190?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/2456632738553392190/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=2456632738553392190" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/2456632738553392190?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/2456632738553392190?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2008/12/redeem-time.html" title="Redeem the Time" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;C0ACSHY9eip7ImA9WxVTEU4.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-2483732057088880331</id><published>2008-12-03T10:56:00.012-05:00</published><updated>2008-12-24T10:16:09.862-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-24T10:16:09.862-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="security" /><category scheme="http://www.blogger.com/atom/ns#" term="privacy" /><title>All Your HTTPS Packets Are Belong To Us</title><content type="html">&lt;p&gt;Occasionally,  I have to work on a restricted network, whether I'm connected via VPN to a remote desktop, or directly to a network.&lt;/p&gt;

&lt;p&gt;I'm also in the position, at times, when I need to retrieve something from my company e-mail account, and for whatever reason (productivity drain or virus infections from pointy haired users) the network proxy automatically blocks access to any sites with the word "mail" in their domain name.&lt;/p&gt;

&lt;p&gt;One of the easiest ways to get around this is to just use HTTPS, because all your traffic to the remote server is immediately encrypted before it is sent on the network.  Only the remote server can decrypt your packets, and if anyone intercepts them, then they look like garbage.  The information in your encrypted packets includes the domain name of the server, because with &lt;a href="http://en.wikipedia.org/wiki/Virtual_hosting#Name-based"&gt;HTTP 1.1 the domain name is sent as an HTTP header&lt;/a&gt;.  So the automatic proxy filter is out of luck, and I am free to access my e-mail account. (Proxies could of course block by IP address, but that just causes a mess.)&lt;/p&gt;

&lt;p&gt;It turns out that some network proxies will &lt;a href="http://www.bluecoat.com/solutions/businessneeds/secureweb/sslvisibility"&gt;automatically generate fake SSL certificates&lt;/a&gt; for every secure site that you access.  (This is probably old news to network administrators.)  The certificates look legit, they include the correct name of the site, but the issuing authority is the proxy device on the network.  I can't say that this is shocking to me, I always knew it was possible, I just hadn't encountered it in the real world.&lt;/p&gt;

&lt;p&gt;I suppose that normally the network administrator would have installed the proper root certificate on every machine on the network, but in my case I pulled up a site with a certificate that matched the site I was accessing, but was issued from an authority that I don't trust (i.e. the network proxy).  Hmm...&lt;/p&gt;

&lt;p&gt;Any normal user wouldn't have understood what was going on, but I was immediately suspicious that my machine had been compromised by some virus, or that the remote site had been compromised for phishing purposes.  I'm glad that neither situation was true, but I'm left yet again with my confidential information compromised in the name of security.&lt;/p&gt;

&lt;p&gt;Think that your credit card number, SSN, personal information, etc. is safe because your using a secure connection.  Think again.  The network administration team is reading everything that you send to the secure site.  The classic &lt;a href="http://en.wikipedia.org/wiki/Man-in-the-middle_attack"&gt;man-in-the-middle attack&lt;/a&gt;.  As always, the weakest link in the chain will be a human being, and now more human beings have access to your information.&lt;/p&gt;

&lt;p&gt;It's a brave new world.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-2483732057088880331?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/2483732057088880331/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=2483732057088880331" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/2483732057088880331?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/2483732057088880331?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2008/12/all-your-https-packets-are-belong-to-us.html" title="All Your HTTPS Packets Are Belong To Us" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DU8HSX08eCp7ImA9WxRWGE0.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-2482906614404294016</id><published>2008-11-04T07:06:00.006-05:00</published><updated>2008-11-04T09:10:38.370-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-04T09:10:38.370-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="clojure" /><title>Clojure: a LISP that has a chance</title><content type="html">&lt;p&gt;Clojure is an interesting new language.  Here's the executive summary:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Clojure is a dynamic programming language that targets the Java Virtual Machine. It is designed to be a general-purpose language, combining the approachability and interactive development of a scripting language with an efficient and robust infrastructure for multithreaded programming. Clojure is a compiled language - it compiles directly to JVM bytecode, yet remains completely dynamic. Every feature supported by Clojure is supported at runtime. Clojure provides easy access to the Java frameworks, with optional type hints and type inference, to ensure that calls to Java can avoid reflection.&lt;/p&gt;

&lt;p&gt;Clojure is a dialect of Lisp, and shares with Lisp the code-as-data philosophy and a powerful macro system. Clojure is predominantly a functional programming language, and features a rich set of immutable, persistent data structures. When mutable state is needed, Clojure offers a software transactional memory system and reactive Agent system that ensure clean, correct, multithreaded designs. &amp;#8212; &lt;a href="http://www.clojure.org/"&gt;www.clojure.org&lt;/a&gt;
&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Each of the key features is exciting to me, a functional LISP that integrates closely with the JVM and has baked-in concurrency.  What's more, I think Clojure has a chance at making it big.  Here's why:

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unique Vision.&lt;/strong&gt; I don't think any new language can survive for long, unless it has a unique vision.  Clojure's unique vision is to bring together a mix of performant, immutable data stuctures, baked-in concurrency, functional style, and close integration with the JVM.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;JVM Integration.&lt;/strong&gt; Rich Hickey had the incredible foresight to see the JVM as a platform to be embraced closely.  This means that not only can you leverage 100% of existing Java code, but your Clojure code compiles to Java bytecode and benefits from the HotSpot JVM's dynamic optimizations.  Compare that to a "from scratch" language that takes years to get a diverse set of libraries and an optimized implementation.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Benevolent Dictator.&lt;/strong&gt; I have always thought that a new LISP (or any new language for that matter) needs a &lt;a href="http://en.wikipedia.org/wiki/Benevolent_Dictator_For_Life"&gt;Benevolent Dictator&lt;/a&gt;.  The BD is the friendly face of the community and sets the tone for how people treat each other.  But more importantly the BD is a dictator who has a strong vision for the language, and will say "no" to feature requests that don't line up with his vision.  This is Rich Hickey, friendly and open to suggestion, but not afraid to say "no."&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;If any of this sounds interesting to you, then check out the &lt;a href="http://www.clojure.org/"&gt;homepage&lt;/a&gt;.  The quickest and easiest way to get involved in the community is to join the &lt;a href="http://groups.google.com/group/clojure"&gt;Google Group&lt;/a&gt;.  Also, if you're into IRC, then check out &lt;a href="irc://irc.freenode.net/%23clojure"&gt;#clojure&lt;/a&gt; on irc.freenode.net.&lt;/p&gt;

&lt;p&gt;I am excited about the future of Clojure, and have really enjoyed working with it so far.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-2482906614404294016?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/2482906614404294016/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=2482906614404294016" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/2482906614404294016?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/2482906614404294016?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2008/11/clojure-lisp-that-has-chance.html" title="Clojure: a LISP that has a chance" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CkQCQHw-eip7ImA9WxRWF0g.&quot;"><id>tag:blogger.com,1999:blog-8086052983184217440.post-5408272340492900798</id><published>2008-11-03T16:40:00.008-05:00</published><updated>2008-11-03T17:12:41.252-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-03T17:12:41.252-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="wordpress" /><category scheme="http://www.blogger.com/atom/ns#" term="blogger" /><category scheme="http://www.blogger.com/atom/ns#" term="blogging" /><title>Migrating from wordpress.com to blogger.com</title><content type="html">&lt;p&gt;I won't go into all of the details about why I switched my blogging platform from &lt;a href="http://www.wordpress.com/"&gt;wordpress.com&lt;/a&gt; to &lt;a href="http://www.blogger.com/"&gt;blogger.com&lt;/a&gt;, but the biggest reason is that Blogger will host a &lt;a href="http://help.blogger.com/bin/answer.py?hl=en&amp;answer=55373"&gt;custom domain&lt;/a&gt; for free, whereas WordPress charges $10/year.  Yes, $10/year is peanuts, and you're free to host your custom domain with them, but I like free.&lt;/p&gt;

&lt;h3&gt;The Switch&lt;/h3&gt;
&lt;p&gt;I had existing posts, and I decided that I wanted existing links to work so that if someone went to &lt;a href="http://paul.stadig.name/2007/10/26/the-state-of-rdf-support-in-ruby-2007/"&gt;http://paul.stadig.name/2007/10/26/the-state-of-rdf-support-in-ruby-2007/&lt;/a&gt; they would not get a 404 or a redirect or a jump page with an "oops-this-site-has-moved-click-here-to-go-to-the-real-site" link. Instead I opted for existing links going to a page that includes the original content, plus a little message that says "You're viewing a 'classic' blog entry.  Check out the &lt;a href="http://paul.stadig.name/"&gt;latest entries&lt;/a&gt;." This can be done with the Blogger &lt;a href="http://help.blogger.com/bin/answer.py?hl=en&amp;answer=68503"&gt;missing files host&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My next problem was that my missing files host would have to be a server somewhere that I had to pay for (hmm...not free).  Instead I decided to use the &lt;a href="http://sites.google.com/"&gt;Google Sites&lt;/a&gt; service for my &lt;a href="http://www.google.com/apps/intl/en/business/index.html"&gt;Google hosted domain&lt;/a&gt;.  I created a site and created a structure to exactly mirror my existing links.  I went into my Google Apps for Your Domain control panel and &lt;a href="http://sites.google.com/support/bin/answer.py?answer=99450&amp;topic=15219"&gt;added a domain mapping&lt;/a&gt; for my new Google Site.  Then I was able to test each post by replacing "pjstadig.wordpress.com" to "paul-classic.stadig.name" in the URL in the browser bar.&lt;/p&gt;

&lt;p&gt;I wouldn't have gone through this whole process if I had hundreds of posts.  However, for the small number of posts that I had, this was an ideal solution.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8086052983184217440-5408272340492900798?l=paul.stadig.name' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://paul.stadig.name/feeds/5408272340492900798/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8086052983184217440&amp;postID=5408272340492900798" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5408272340492900798?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8086052983184217440/posts/default/5408272340492900798?v=2" /><link rel="alternate" type="text/html" href="http://paul.stadig.name/2008/11/migrating-from-wordpresscom-to.html" title="Migrating from wordpress.com to blogger.com" /><author><name>paul</name><uri>http://www.blogger.com/profile/14647609048389725132</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_fF4WuoHXzwE/SRBjhVfrRCI/AAAAAAAAACs/eFo7YomwlZA/S220/n15621912_35516342_3656.jpg" /></author><thr:total>0</thr:total></entry></feed>

