blog/wayback/@done/sjs More Scheming with Haskell.html
2011-12-11 01:04:10 -08:00

646 lines
31 KiB
HTML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>sjs: More Scheming with Haskell</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<!-- Include Mephisto's default RSS feed -->
<link href="http://sami.samhuri.net/feed/atom.xml" rel="alternate" type="application/atom+xml" />
<script src="http://web.archive.org/web/20070616135646js_/http://sami.samhuri.net/javascripts/prototype.js" type="text/javascript"></script>
<link href="http://web.archive.org/web/20070616135646cs_/http://sami.samhuri.net/stylesheets/application.css" rel="stylesheet" type="text/css" />
</head>
<body>
<!-- BEGIN WAYBACK TOOLBAR INSERT -->
<script type="text/javascript" src="http://staticweb.archive.org/js/disclaim-element.js" ></script>
<script type="text/javascript" src="http://staticweb.archive.org/js/graph-calc.js" ></script>
<script type="text/javascript" src="http://staticweb.archive.org/jflot/jquery.min.js" ></script>
<script type="text/javascript">
//<![CDATA[
var firstDate = 820454400000;
var lastDate = 1325375999999;
var wbPrefix = "http://web.archive.org/web/";
var wbCurrentUrl = "http://sami.samhuri.net/2007/6/14/more-scheming-with-haskell";
var curYear = -1;
var curMonth = -1;
var yearCount = 16;
var firstYear = 1996;
var imgWidth=400;
var yearImgWidth = 25;
var monthImgWidth = 2;
var trackerVal = "none";
var displayDay = "16";
var displayMonth = "Jun";
var displayYear = "2007";
var prettyMonths = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
function showTrackers(val) {
if(val == trackerVal) {
return;
}
if(val == "inline") {
document.getElementById("displayYearEl").style.color = "#ec008c";
document.getElementById("displayMonthEl").style.color = "#ec008c";
document.getElementById("displayDayEl").style.color = "#ec008c";
} else {
document.getElementById("displayYearEl").innerHTML = displayYear;
document.getElementById("displayYearEl").style.color = "#ff0";
document.getElementById("displayMonthEl").innerHTML = displayMonth;
document.getElementById("displayMonthEl").style.color = "#ff0";
document.getElementById("displayDayEl").innerHTML = displayDay;
document.getElementById("displayDayEl").style.color = "#ff0";
}
document.getElementById("wbMouseTrackYearImg").style.display = val;
document.getElementById("wbMouseTrackMonthImg").style.display = val;
trackerVal = val;
}
function getElementX2(obj) {
var thing = jQuery(obj);
if((thing == undefined)
|| (typeof thing == "undefined")
|| (typeof thing.offset == "undefined")) {
return getElementX(obj);
}
return Math.round(thing.offset().left);
}
function trackMouseMove(event,element) {
var eventX = getEventX(event);
var elementX = getElementX2(element);
var xOff = eventX - elementX;
if(xOff < 0) {
xOff = 0;
} else if(xOff > imgWidth) {
xOff = imgWidth;
}
var monthOff = xOff % yearImgWidth;
var year = Math.floor(xOff / yearImgWidth);
var yearStart = year * yearImgWidth;
var monthOfYear = Math.floor(monthOff / monthImgWidth);
if(monthOfYear > 11) {
monthOfYear = 11;
}
// 1 extra border pixel at the left edge of the year:
var month = (year * 12) + monthOfYear;
var day = 1;
if(monthOff % 2 == 1) {
day = 15;
}
var dateString =
zeroPad(year + firstYear) +
zeroPad(monthOfYear+1,2) +
zeroPad(day,2) + "000000";
var monthString = prettyMonths[monthOfYear];
document.getElementById("displayYearEl").innerHTML = year + 1996;
document.getElementById("displayMonthEl").innerHTML = monthString;
// looks too jarring when it changes..
//document.getElementById("displayDayEl").innerHTML = zeroPad(day,2);
var url = wbPrefix + dateString + '/' + wbCurrentUrl;
document.getElementById('wm-graph-anchor').href = url;
//document.getElementById("wmtbURL").value="evX("+eventX+") elX("+elementX+") xO("+xOff+") y("+year+") m("+month+") monthOff("+monthOff+") DS("+dateString+") Moy("+monthOfYear+") ms("+monthString+")";
if(curYear != year) {
var yrOff = year * yearImgWidth;
document.getElementById("wbMouseTrackYearImg").style.left = yrOff + "px";
curYear = year;
}
if(curMonth != month) {
var mtOff = year + (month * monthImgWidth) + 1;
document.getElementById("wbMouseTrackMonthImg").style.left = mtOff + "px";
curMonth = month;
}
}
//]]>
</script>
<style type="text/css">body{margin-top:0!important;padding-top:0!important;min-width:800px!important;}#wm-ipp a:hover{text-decoration:underline!important;}</style>
<div id="wm-ipp" style="display:none; position:relative;padding:0 5px;min-height:70px;min-width:800px; z-index:9000;">
<div id="wm-ipp-inside" style="position:fixed;padding:0!important;margin:0!important;width:97%;min-width:780px;border:5px solid #000;border-top:none;background-image:url(http://staticweb.archive.org/images/toolbar/wm_tb_bk_trns.png);text-align:center;-moz-box-shadow:1px 1px 3px #333;-webkit-box-shadow:1px 1px 3px #333;box-shadow:1px 1px 3px #333;font-size:11px!important;font-family:'Lucida Grande','Arial',sans-serif!important;">
<table style="border-collapse:collapse;margin:0;padding:0;width:100%;"><tbody><tr>
<td style="padding:10px;vertical-align:top;min-width:110px;">
<a href="http://wayback.archive.org/web/" title="Wayback Machine home page" style="background-color:transparent;border:none;"><img src="http://staticweb.archive.org/images/toolbar/wayback-toolbar-logo.png" alt="Wayback Machine" width="110" height="39" border="0"/></a>
</td>
<td style="padding:0!important;text-align:center;vertical-align:top;width:100%;">
<table style="border-collapse:collapse;margin:0 auto;padding:0;width:570px;"><tbody><tr>
<td style="padding:3px 0;" colspan="2">
<form target="_top" method="get" action="http://wayback.archive.org/web/form-submit.jsp" name="wmtb" id="wmtb" style="margin:0!important;padding:0!important;"><input type="text" name="url" id="wmtbURL" value="http://sami.samhuri.net/2007/6/14/more-scheming-with-haskell" style="width:400px;font-size:11px;font-family:'Lucida Grande','Arial',sans-serif;" onfocus="javascript:this.focus();this.select();" /><input type="hidden" name="type" value="replay" /><input type="hidden" name="date" value="20070616135646" /><input type="submit" value="Go" style="font-size:11px;font-family:'Lucida Grande','Arial',sans-serif;margin-left:5px;" /><span id="wm_tb_options" style="display:block;"></span></form>
</td>
<td style="vertical-align:bottom;padding:5px 0 0 0!important;" rowspan="2">
<table style="border-collapse:collapse;width:110px;color:#99a;font-family:'Helvetica','Lucida Grande','Arial',sans-serif;"><tbody>
<!-- NEXT/PREV MONTH NAV AND MONTH INDICATOR -->
<tr style="width:110px;height:16px;font-size:10px!important;">
<td style="padding-right:9px;font-size:11px!important;font-weight:bold;text-transform:uppercase;text-align:right;white-space:nowrap;overflow:visible;" nowrap="nowrap">
May
</td>
<td id="displayMonthEl" style="background:#000;color:#ff0;font-size:11px!important;font-weight:bold;text-transform:uppercase;width:34px;height:15px;padding-top:1px;text-align:center;" title="You are here: 13:56:46 Jun 16, 2007">JUN</td>
<td style="padding-left:9px;font-size:11px!important;font-weight:bold;text-transform:uppercase;white-space:nowrap;overflow:visible;" nowrap="nowrap">
<a href="http://sami.samhuri.net/2007/6/14/more-scheming-with-haskell" style="text-decoration:none;color:#33f;font-weight:bold;background-color:transparent;border:none;" title="17 Jul 2007"><strong>JUL</strong></a>
</td>
</tr>
<!-- NEXT/PREV CAPTURE NAV AND DAY OF MONTH INDICATOR -->
<tr>
<td style="padding-right:9px;white-space:nowrap;overflow:visible;text-align:right!important;vertical-align:middle!important;" nowrap="nowrap">
<img src="http://staticweb.archive.org/images/toolbar/wm_tb_prv_off.png" alt="Previous capture" width="14" height="16" border="0" />
</td>
<td id="displayDayEl" style="background:#000;color:#ff0;width:34px;height:24px;padding:2px 0 0 0;text-align:center;font-size:24px;font-weight: bold;" title="You are here: 13:56:46 Jun 16, 2007">16</td>
<td style="padding-left:9px;white-space:nowrap;overflow:visible;text-align:left!important;vertical-align:middle!important;" nowrap="nowrap">
<a href="http://sami.samhuri.net/2007/6/14/more-scheming-with-haskell" title="9:35:16 Jul 17, 2007" style="background-color:transparent;border:none;"><img src="http://staticweb.archive.org/images/toolbar/wm_tb_nxt_on.png" alt="Next capture" width="14" height="16" border="0"/></a>
</td>
</tr>
<!-- NEXT/PREV YEAR NAV AND YEAR INDICATOR -->
<tr style="width:110px;height:13px;font-size:9px!important;">
<td style="padding-right:9px;font-size:11px!important;font-weight: bold;text-align:right;white-space:nowrap;overflow:visible;" nowrap="nowrap">
2006
</td>
<td id="displayYearEl" style="background:#000;color:#ff0;font-size:11px!important;font-weight: bold;padding-top:1px;width:34px;height:13px;text-align:center;" title="You are here: 13:56:46 Jun 16, 2007">2007</td>
<td style="padding-left:9px;font-size:11px!important;font-weight: bold;white-space:nowrap;overflow:visible;" nowrap="nowrap">
<a href="http://sami.samhuri.net/2007/6/14/more-scheming-with-haskell" style="text-decoration:none;color:#33f;font-weight:bold;background-color:transparent;border:none;" title="20 Aug 2008"><strong>2008</strong></a>
</td>
</tr>
</tbody></table>
</td>
</tr>
<tr>
<td style="vertical-align:middle;padding:0!important;">
<a href="http://wayback.archive.org/web/20070616135646*/http://sami.samhuri.net/2007/6/14/more-scheming-with-haskell" style="color:#33f;font-size:11px;font-weight:bold;background-color:transparent;border:none;" title="See a list of every capture for this URL"><strong>12 captures</strong></a>
<div style="margin:0!important;padding:0!important;color:#666;font-size:9px;padding-top:2px!important;white-space:nowrap;" title="Timespan for captures of this URL">16 Jun 07 - 14 Apr 09</div>
</td>
<td style="padding:0!important;">
<a style="position:relative; white-space:nowrap; width:400px;height:27px;" href="" id="wm-graph-anchor">
<div id="wm-ipp-sparkline" style="position:relative; white-space:nowrap; width:400px;height:27px;background-color:#fff;cursor:pointer;border-right:1px solid #ccc;" title="Explore captures for this URL">
<img id="sparklineImgId" style="position:absolute; z-index:9012; top:0px; left:0px;"
onmouseover="showTrackers('inline');"
onmouseout="showTrackers('none');"
onmousemove="trackMouseMove(event,this)"
alt="sparklines"
width="400"
height="27"
border="0"
src="http://wayback.archive.org/jsp/graph.jsp?graphdata=400_27_1996:-1:000000000000_1997:-1:000000000000_1998:-1:000000000000_1999:-1:000000000000_2000:-1:000000000000_2001:-1:000000000000_2002:-1:000000000000_2003:-1:000000000000_2004:-1:000000000000_2005:-1:000000000000_2006:-1:000000000000_2007:5:000001111010_2008:-1:101010010000_2009:-1:011100000000_2010:-1:000000000000_2011:-1:000000000000"></img>
<img id="wbMouseTrackYearImg"
style="display:none; position:absolute; z-index:9010;"
width="25"
height="27"
border="0"
src="http://staticweb.archive.org/images/toolbar/transp-yellow-pixel.png"></img>
<img id="wbMouseTrackMonthImg"
style="display:none; position:absolute; z-index:9011; "
width="2"
height="27"
border="0"
src="http://staticweb.archive.org/images/toolbar/transp-red-pixel.png"></img>
</div>
</a>
</td>
</tr></tbody></table>
</td>
<td style="text-align:right;padding:5px;width:65px;font-size:11px!important;">
<a href="javascript:;" onclick="document.getElementById('wm-ipp').style.display='none';" style="display:block;padding-right:18px;background:url(http://staticweb.archive.org/images/toolbar/wm_tb_close.png) no-repeat 100% 0;color:#33f;font-family:'Lucida Grande','Arial',sans-serif;margin-bottom:23px;background-color:transparent;border:none;" title="Close the toolbar">Close</a>
<a href="http://faq.web.archive.org/" style="display:block;padding-right:18px;background:url(http://staticweb.archive.org/images/toolbar/wm_tb_help.png) no-repeat 100% 0;color:#33f;font-family:'Lucida Grande','Arial',sans-serif;background-color:transparent;border:none;" title="Get some help using the Wayback Machine">Help</a>
</td>
</tr></tbody></table>
</div>
</div>
<script type="text/javascript">
var wmDisclaimBanner = document.getElementById("wm-ipp");
if(wmDisclaimBanner != null) {
disclaimElement(wmDisclaimBanner);
}
</script>
<!-- END WAYBACK TOOLBAR INSERT -->
<div id="container">
<div id="header">
<h1><span><a href="http://sami.samhuri.net/">sjs</a></span></h1>
<h2>geeky ramblings</h2>
</div>
<div id="page">
<div id="content">
<!--
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<rdf:Description
rdf:about=""
trackback:ping=""
dc:title="More Scheming with Haskell"
dc:identifier="/2007/6/14/more-scheming-with-haskell"
dc:description="<p>It's been a little while since I wrote about Haskell and the <a href="http://sami.samhuri.net/2007/5/3/a-scheme-parser-in-haskell-part-1">Scheme interpreter</a> I've been using to learn and play with both Haskell and Scheme. I finis..."
dc:creator="sjs"
dc:date="June 14, 2007 01:09" />
</rdf:RDF>
-->
<div class="hentry" id="article-65">
<h2 class="entry-title">
<a href="http://sami.samhuri.net/2007/6/14/more-scheming-with-haskell">More Scheming with Haskell</a>
<span class="comment_count">2</span>
</h2>
<div class="vcard">
Posted by <span class="fn">sjs</span>
</div>
<abbr class="published" title="2007-06-14T01:09:00+00:00">on Thursday, June 14</abbr>
<br class="clear" />
<div class="entry-content">
<p>It's been a little while since I wrote about Haskell and the <a href="http://sami.samhuri.net/2007/5/3/a-scheme-parser-in-haskell-part-1">Scheme interpreter</a> I've been using to learn and play with both Haskell and Scheme. I finished the tutorial and got myself a working Scheme interpreter and indeed it has been fun to use it for trying out little things now and then. (Normally I would use Emacs or Dr. Scheme for that sort of thing.) There certainly are <a href="http://www.lshift.net/blog/2007/06/11/folds-and-continuation-passing-style">interesting things</a> to try floating around da intranet. And also things to read and learn from, such as <a href="http://cubiclemuses.com/cm/blog/tags/Misp">misp</a> (via <a href="http://moonbase.rydia.net/mental/blog/programming/misp-is-a-lisp">Moonbase</a>).</p>
<p><em>I'm going to describe two new features of my Scheme in this post. The second one is more interesting and was more fun to implement (cond).</em></p>
<h3>Pasing Scheme integers</h3>
<p>Last time I left off at parsing <a href="http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_sec_6.3.5">R5RS compliant numbers</a>, which is exercise 3.3.4 if you're following along the tutorial. Only integers in binary, octal, decimal, and hexadecimal are parsed right now. The syntaxes for those are <code>#b101010</code>, <code>#o52</code>, <code>42</code> (or <code>#d42</code>), and <code>#x2a</code>, respectively. To parse these we use the <code>readOct</code>, <code>readDec</code>, <code>readHex</code>, and <code>readInt</code> functions provided by the Numeric module, and import them thusly:</p>
<pre><code>import Numeric (readOct, readDec, readHex, readInt)</code></pre>
<p>In order to parse binary digits we need to write a few short functions to help us out. For some reason I couldn't find <code>binDigit</code>, <code>isBinDigit</code> and <code>readBin</code> in their respective modules but luckily they're trivial to implement. The first two are self-explanatory, as is the third if you look at the <a href="http://www.cse.ogi.edu/~diatchki/MonadTransformers/pfe.cgi?Numeric">implementation</a> of its relatives for larger bases. In a nutshell <code>readBin</code> says to: "read an integer in base 2, validating digits with <code>isBinDigit</code>."</p>
<pre><code>-- parse a binary digit, analagous to decDigit, octDigit, hexDigit
binDigit :: Parser Char
binDigit = oneOf "01"
-- analogous to isDigit, isOctdigit, isHexDigit
isBinDigit :: Char -> Bool
isBinDigit c = (c == '0' || c == '1')
-- analogous to readDec, readOct, readHex
readBin :: (Integral a) => ReadS a
readBin = readInt 2 isBinDigit digitToInt</code></pre>
<p>The next step is to augment <code>parseNumber</code> so that it can handle R5RS numbers in addition to regular decimal numbers. To refresh, the tutorial's <code>parseNumber</code> function looks like this:</p>
<pre><code>parseNumber :: Parser LispVal
parseNumber = liftM (Number . read) $ many1 digit</code></pre>
<p>Three more lines in this function will give us a decent starting point:</p>
<pre><code>parseNumber = do char '#'
base <- oneOf "bdox"
parseDigits base
<|> (many1 digit >>= return . Number . read)</code></pre>
<p>Translation: First look for an R5RS style base, and if found call <code>parseDigits</code> with the given base to do the dirty work. If that fails then fall back to parsing a boring old string of decimal digits.</p>
<p>That brings us to actually parsing the numbers. <code>parseDigits</code> is simple, but there might be a more Haskell-y way of doing this.</p>
<pre><code>-- Parse a string of digits in the given base.
parseDigits :: Char -> Parser LispVal
parseDigits base = do digits <- many1 d
return . Number . fst . head . f $ digits
where f = case base of
'b' -> readBin
'd' -> readDec
'o' -> readOct
'x' -> readHex
d = case base of
'b' -> binDigit
'd' -> digit
'o' -> octDigit
'x' -> hexDigit</code></pre>
<p>The trickiest part of all this was figuring out how to use the various <code>readFoo</code> functions properly. They return a list of pairs so <code>head</code> grabs the first pair and <code>fst</code> grabs the first element of the pair. Once I had that straight it was smooth sailing. Having done this, parsing R5RS characters (#\a, #\Z, #\?, ...) is a breeze so I won't bore you with that.</p>
<h3>The cond function</h3>
<p>It still takes me some time to knit together meaningful Haskell statements. Tonight I spent said time cobbling together an implementation of <a href="http://schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_sec_4.1.5">cond</a> as a new special form. Have a look at the code. The explanation follows.</p>
<table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">eval env (List (Atom &quot;cond&quot; : List (Atom &quot;else&quot; : exprs) : [])) =<tt>
</tt> liftM last $ mapM (eval env) exprs<tt>
</tt>eval env (List (Atom &quot;cond&quot; : List (pred : conseq) : rest)) = <tt>
</tt> do result &lt;- eval env $ pred<tt>
</tt> case result of<tt>
</tt> Bool False -&gt; case rest of<tt>
</tt> [] -&gt; return $ List []<tt>
</tt> _ -&gt; eval env $ List (Atom &quot;cond&quot; : rest)<tt>
</tt> _ -&gt; liftM last $ mapM (eval env) conseq</pre></td>
</tr></table>
<ul>
<li><b>Lines 1-2:</b> Handle <code>else</code> clauses by evaluating the given expression(s), returning the last result. It must come first or it's overlapped by the next pattern.</li>
<li><b>Line 3:</b> Evaluate a <code>cond</code> by splitting the first condition into <strong>predicate</strong> and <strong>consequence</strong>, tuck the remaining conditions into <code>rest</code> for later.</li>
<li><b>Line 4:</b> Evaluate <code>pred</code></li>
<li><b>Line 5:</b> and if the result is:</li>
<li><b>Line 6:</b> <code>#f</code> then look at the rest of the conditions.</li>
<li><b>Line 7:</b> If there are no more conditions return the empty list.</li>
<li><b>Line 8:</b> Otherwise call ourselves recursively with the remaining conditions.</li>
<li><b>Line 9:</b> Anything other than <code>#f</code> is considered true and causes <code>conseq</code> to be evaluated and returned. Like <code>else</code>, <code>conseq</code> can be a sequence of expressions.</li>
</ul>
<p>So far my Scheme weighs in at 621 lines, 200 more than the tutorial's final code listing. Hopefully I'll keep adding things on my TODO list and it will grow a little bit more. Now that I have <code>cond</code> it will be more fun to expand my stdlib.scm as well.</p>
</div>
<ul class="meta">
<li>
Tags: <a href="http://sami.samhuri.net/tags/coding">coding</a>&nbsp;<a href="http://sami.samhuri.net/tags/haskell">haskell</a>&nbsp;<a href="http://sami.samhuri.net/tags/scheme">scheme</a>&nbsp;
</li>
<li>
Meta:
<a href="http://sami.samhuri.net/2007/6/14/more-scheming-with-haskell">2 comments</a>,
<a href="http://sami.samhuri.net/2007/6/14/more-scheming-with-haskell">permalink</a>
</li>
</ul>
</div>
<h5><a name="comments">Comments</a></h5>
<p><a href="http://sami.samhuri.net/2007/6/14/more-scheming-with-haskell#comment-form">Leave a response</a></p>
<div id="comments_div">
<ol id="comments" class="comments">
<li class="comment" id="comment-69">
<div class="author">
<cite><span class="author"><span>whoopee</span></span></cite> &#150;
<abbr title="June 14, 2007 08:57"><span class="date">June 14, 2007 @ 08:57 AM</span></abbr>
</div>
<div class="content">
<p>thanks! just last night i was thinking - hmm, i have this haskell program that takes what i want to be an Integer from the command line - how do i enforce this? your post is relevant to these issues...thanks!</p>
</div>
</li>
<li class="comment" id="comment-72">
<div class="author">
<cite><span class="author"><a href="http://sami.samhuri.net/">sjs</a></span></cite> &#150;
<abbr title="June 15, 2007 09:39"><span class="date">June 15, 2007 @ 09:39 AM</span></abbr>
</div>
<div class="content">
<p>Sure, it's good to know that someone found something I said useful. :)</p>
</div>
</li>
</ol>
</div>
<form id="comment-form" method="post" action="http://sami.samhuri.net/2007/6/14/more-scheming-with-haskell/comments#comment-form">
<fieldset>
<legend>Comment</legend>
<p>
<label class="text" for="comment_author">Name:</label><br/>
<input type="text" id="comment_author" name="comment[author]" value="" />
</p>
<p>
<label class="text" for="comment_author_email">Email Address:</label><br />
<input type="text" id="comment_author_email" name="comment[author_email]" value="" />
</p>
<p>
<label class="text" for="comment_author_url">Website:</label><br />
<input type="text" id="comment_author_url" name="comment[author_url]" value="" />
</p>
<p>
<label class="text" for="comment_body">Comment:</label><br />
<textarea id="comment_body" name="comment[body]"></textarea>
</p>
<div class="formactions">
<input type="submit" value="Post comment" class="submit" />
</div>
</fieldset>
</form>
</div>
<div id="sidebar">
<div class="sidebar-node">
<div id="search" class="search">
<form action="http://sami.samhuri.net/search" id="sform" method="get" name="sform">
<p><input type="text" id="q" name="q" value="" /></p>
</form>
</div>
</div>
<div class="sidebar-node">
<h3>Tags</h3>
<ul>
<li><a href="http://sami.samhuri.net/tags/activerecord">activerecord</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/amusement">amusement</a> (6)</li>
<li><a href="http://sami.samhuri.net/tags/apple">apple</a> (7)</li>
<li><a href="http://sami.samhuri.net/tags/bdd">bdd</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/bootcamp">bootcamp</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/buffalo">buffalo</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/bundle">bundle</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/cheat">cheat</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/coding">coding</a> (22)</li>
<li><a href="http://sami.samhuri.net/tags/cool">cool</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/coverflow">coverflow</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/crazy">crazy</a> (2)</li>
<li><a href="http://sami.samhuri.net/tags/digg">digg</a> (2)</li>
<li><a href="http://sami.samhuri.net/tags/drm">drm</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/dtrace">dtrace</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/extensions">extensions</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/firefox">firefox</a> (2)</li>
<li><a href="http://sami.samhuri.net/tags/framework">framework</a> (2)</li>
<li><a href="http://sami.samhuri.net/tags/funny">funny</a> (2)</li>
<li><a href="http://sami.samhuri.net/tags/fuse">fuse</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/games">games</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/german">german</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/haskell">haskell</a> (4)</li>
<li><a href="http://sami.samhuri.net/tags/inspirado">inspirado</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/iphone">iphone</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/itunes">itunes</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/i_laughed_i_cried">i_laughed_i_cried</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/keyboard">keyboard</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/life">life</a> (6)</li>
<li><a href="http://sami.samhuri.net/tags/linux">linux</a> (4)</li>
<li><a href="http://sami.samhuri.net/tags/mac%20os%20x">mac os x</a> (7)</li>
<li><a href="http://sami.samhuri.net/tags/macosx">macosx</a> (4)</li>
<li><a href="http://sami.samhuri.net/tags/mediawiki">mediawiki</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/mephisto">mephisto</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/migrations">migrations</a> (2)</li>
<li><a href="http://sami.samhuri.net/tags/munich">munich</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/mysql">mysql</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/networking">networking</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/parallels">parallels</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/photo">photo</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/php">php</a> (4)</li>
<li><a href="http://sami.samhuri.net/tags/python">python</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/rails">rails</a> (15)</li>
<li><a href="http://sami.samhuri.net/tags/regex">regex</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/rest">rest</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/ruby">ruby</a> (8)</li>
<li><a href="http://sami.samhuri.net/tags/scheme">scheme</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/school">school</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/seekport">seekport</a> (2)</li>
<li><a href="http://sami.samhuri.net/tags/snippets">snippets</a> (2)</li>
<li><a href="http://sami.samhuri.net/tags/technology">technology</a> (6)</li>
<li><a href="http://sami.samhuri.net/tags/test/spec">test/spec</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/textmate">textmate</a> (5)</li>
<li><a href="http://sami.samhuri.net/tags/typo">typo</a> (2)</li>
<li><a href="http://sami.samhuri.net/tags/ubuntu">ubuntu</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/usability">usability</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/userscript">userscript</a> (2)</li>
<li><a href="http://sami.samhuri.net/tags/wikipediafs">wikipediafs</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/windows">windows</a> (3)</li>
<li><a href="http://sami.samhuri.net/tags/work">work</a> (1)</li>
<li><a href="http://sami.samhuri.net/tags/zend">zend</a> (2)</li>
<li><a href="http://sami.samhuri.net/tags/zsh">zsh</a> (1)</li>
</ul>
</div>
<div class="sidebar-node">
<p><a href="http://mephistoblog.com/" class="powered"><img alt="mephisto-badge-tiny" src="http://web.archive.org/web/20070616135646im_/http://sami.samhuri.net/images/mephisto-badge-tiny.png" /></a></p>
</div>
</div>
<br style="clear:both;" />
</div>
<div id="footer">
<hr />
<p><a href="http://sami.samhuri.net/">sjs</a></p>
<ul>
<li>powered by <a href="http://mephistoblog.com/">Mephisto</a> /
styled with <a href="http://quotedprintable.com/pages/scribbish">scribbish</a></li>
</ul>
</div>
</div>
<script src="http://web.archive.org/web/20070616135646js_/http://www.google-analytics.com/urchin.js" type="text/javascript"> </script>
<script type="text/javascript"> _uacct = "UA-214054-3"; urchinTracker(); </script>
</body>
</html>
<!--
FILE ARCHIVED ON 13:56:46 Jun 16, 2007 AND RETRIEVED FROM THE
INTERNET ARCHIVE ON 2:58:13 Aug 21, 2011.
JAVASCRIPT APPENDED BY WAYBACK MACHINE, COPYRIGHT INTERNET ARCHIVE.
ALL OTHER CONTENT MAY ALSO BE PROTECTED BY COPYRIGHT (17 U.S.C.
SECTION 108(a)(3)).
-->