<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7435412383881764254</id><updated>2011-12-11T18:07:40.982-08:00</updated><category term='smtp'/><category term='single dispatch'/><category term='Application Platform'/><category term='Eclipse RCP'/><category term='Mnesia'/><category term='Apple'/><category term='RIA'/><category term='Concurrency'/><category term='Dayfindr.com'/><category term='Software Development Process'/><category term='Charts'/><category term='multiple dispatch'/><category term='TDD'/><category term='Acid3'/><category term='OTP'/><category term='Safari'/><category term='Distributed databases'/><category term='Apache'/><category term='Powerpoint'/><category term='Unit Testing'/><category term='Rake'/><category term='HTML5'/><category term='Scalaris'/><category term='Web server performance'/><category term='Make'/><category term='Google Wave'/><category term='REST'/><category term='Macbook Pro'/><category term='Opera'/><category term='XPlanner'/><category term='erlang exchange'/><category term='Google OS'/><category term='Mochiweb'/><category term='Yaws'/><category term='Java'/><category term='Presentations'/><category term='Google Chrome'/><category term='Firefox'/><category term='Iterative Development'/><category term='FUSE'/><category term='Observer Pattern'/><category term='Rakefile'/><category term='Visitor pattern'/><category term='Quicksort'/><category term='IE'/><category term='Keynote'/><category term='Internet Explorer'/><category term='DHTML'/><category term='Agile Development'/><category term='Object Oriented Programming'/><category term='Erlang factory'/><category term='Big Mac Index'/><category term='Erlang'/><title type='text'>21st Century Code Works</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>35</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-4582542653477526639</id><published>2010-02-04T09:40:00.000-08:00</published><updated>2010-07-23T01:45:40.066-07:00</updated><title type='text'>Goodbye Blogger</title><content type='html'>&lt;p&gt;2010 has brought a few changes. I'm now a freelance software engineer with my own company, 1011 Ltd, based in London.  This is also ideal opportunity to do something I've been planning for a while - stop using Blogger.&lt;/p&gt;&lt;p&gt;Blogger used to be fine, but it just hasn't kept up. Wordpress, for example, (and there are plenty of others) offers so much more in terms of usability, customizability and administration. Some real pain points for me are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;In Blogger you have to work with a minuscule editing window that you cannot enlarge. Wordpress has a fullscreen mode. &lt;/li&gt;&lt;li&gt;You can download and install Wordpress to your own domain, even modify it as you like, and have ownership of all your posts.&lt;/li&gt;&lt;li&gt;Blogger has doesn't have proper previews using the blog template, whereas the preview in Wordpress is an &lt;i&gt;actual&lt;/i&gt; preview.&lt;/li&gt;&lt;li&gt;At this very moment the image feature is broken&lt;/li&gt;&lt;li&gt;The html in uneditable&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;p&gt;It feels to me like Google bought Blogger and then just stop working on it. A bit like Microsoft &lt;a href="http://www.telegraph.co.uk/technology/6530310/Firefox-new-version-planned-for-December-as-browser-celebrates-fifth-birthday.html?"&gt;disbanding the IE team in 2004&lt;/a&gt;. &lt;/p&gt; &lt;p&gt;Find me at my new blog, &lt;a href="http://www.1011ltd.com/web/blog"&gt;www.1011ltd.com/web/blog&lt;/a&gt;&lt;/p&gt;&lt;span class="Apple-style-span"  style="color:#FF0000;"&gt;[Update: And as you can see, the Spam protection is woeful]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-4582542653477526639?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/4582542653477526639/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=4582542653477526639' title='23 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/4582542653477526639'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/4582542653477526639'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2010/02/goodbye-blogger.html' title='Goodbye Blogger'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>23</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-3951992122970155328</id><published>2009-08-23T08:40:00.000-07:00</published><updated>2009-08-23T09:30:42.253-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Google Wave'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>My Google Wave Client</title><content type='html'>Google released a &lt;a href="http://code.google.com/p/wave-protocol/"&gt;reference implementation&lt;/a&gt; of a Wave client and server in July, written in Java, that you can download and experiment with. When Mickaël Rémond wrote about &lt;a href="http://www.process-one.net/en/blogs/article/using_google_wave_reference_implementation_with_ejabberd/"&gt;using the reference implementation with ejabberd&lt;/a&gt;, I decided that it was time to get my hands dirty.&lt;br /&gt;&lt;br /&gt;I got the reference implementation working, and I could chat with multiple participants in the console clients. However, I wanted to know more about what was happening behind the scenes in the client, and more importantly, the server (and how operation tranforms work).&lt;br /&gt;&lt;br /&gt;So I decided to write my own client in Erlang that communicates with the reference server. This way I could investigate what was happening in the client and the server, and gain more knowledge about Wave. It required a bit of reverse engineering, and messing around with protocol buffers (the client communicates with the server using these), but it works nicely.&lt;br /&gt;&lt;br /&gt;I've created a video to demonstrate it. Happy watching!&lt;br /&gt;&lt;br /&gt;&lt;object width="400" height="300"&gt;&lt;param name="allowfullscreen" value="true" /&gt;&lt;param name="allowscriptaccess" value="always" /&gt;&lt;param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=6233461&amp;amp;server=vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=&amp;amp;fullscreen=1" /&gt;&lt;embed src="http://vimeo.com/moogaloop.swf?clip_id=6233461&amp;amp;server=vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=&amp;amp;fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="400" height="300"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;p&gt;&lt;a href="http://vimeo.com/6233461"&gt;Vimeo link&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;P.S. I used ngerakines's &lt;a href="http://github.com/ngerakines/erlang_protobuffs/tree/master"&gt;implementation&lt;/a&gt; of Erlang prototocol buffers, and the &lt;a href="http://nitrogenproject.com/"&gt;Nitrogen Web Framework.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;P.P.S. I'm going on holiday for 2 weeks, so if I don't reply to comments, please be patient. I will do so when I get back...&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nitrogenproject.com/"&gt; &lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-3951992122970155328?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/3951992122970155328/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=3951992122970155328' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/3951992122970155328'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/3951992122970155328'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2009/08/my-google-wave-client.html' title='My Google Wave Client'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-6202057611761079672</id><published>2009-07-08T02:04:00.000-07:00</published><updated>2009-07-08T02:22:09.355-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='HTML5'/><category scheme='http://www.blogger.com/atom/ns#' term='Google OS'/><category scheme='http://www.blogger.com/atom/ns#' term='Google Chrome'/><title type='text'>Google OS - I believe I was right</title><content type='html'>&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;disclosure: I own Google shares&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;I'm not one for blowing my own trumpet, but sometimes it feels good to be right. A month ago I &lt;a href="http://21ccw.blogspot.com/2009/06/look-out-rias-html5-is-coming.html"&gt;blogged&lt;/a&gt; about HTML5, and I said the following:&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;"I believe it is part of Google's long term strategy to make the browser the de-facto application platform. To displace Windows as the dominant aplication platform. You can see it already with Chrome, how the tabs on the browser have become the new taskbar, and the integration of browser tabs as "applications" into the host OS. For example, features like local file storage and the canvas provide a path for Google Docs to become more like MS Office. You can be sure that some of the drive behind the HTML5 standard is coming from the Google docs team."&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;Yesterday Google announced Google OS: &lt;a href="http://googleblog.blogspot.com/2009/07/introducing-google-chrome-os.html"&gt;http://googleblog.blogspot.com/2009/07/introducing-google-chrome-os.html&lt;/a&gt;. Google OS will be based on Chrome and a Linux kernel. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;From the announcement:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;"For application developers, the web is the platform. All web-based applications will automatically work and new applications can be written using your favorite web technologies. And of course, these apps will run not only on Google Chrome OS, but on any standards-based browser on Windows, Mac and Linux thereby giving developers the largest user base of any platform."&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;I will admit that I didn't think they would actually write an OS to support this strategy, but the strategy is clear. Unfortunately we'll have to wait at least a year to see it in action...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-6202057611761079672?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/6202057611761079672/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=6202057611761079672' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/6202057611761079672'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/6202057611761079672'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2009/07/google-os-i-believe-i-was-right.html' title='Google OS - I believe I was right'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-5669561164847906526</id><published>2009-07-02T13:32:00.000-07:00</published><updated>2009-07-05T02:27:32.029-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang factory'/><title type='text'>Erlang Factory 2009 - New Kids on the Erlang Block</title><content type='html'>&lt;div style="text-align: left;"&gt;I went to Erlang Factory again this year thanks to my &lt;a href="http://www.zuhlke.co.uk/"&gt;employer&lt;/a&gt;, and it was even more exciting than &lt;a href="http://21ccw.blogspot.com/2008/07/notes-from-erlang-exchange.html"&gt;last year's&lt;/a&gt; event. There were some great speakers (Joe Armstrong - entertaining as ever, Simon Peyton Jones), I got to meet some interesting people (e.g. Rusty Klophaus, creator of the Nitrogen web framework) and I got to see some awesome projects. Although Erlang is experiencing a surge of interest, it is by no means "new". it has been used for years in robust and highly scalable applications. That is why I refer to the "new" - as opposed to the solid Erlang VM, tools, libraries and frameworks that have been around for at least a decade.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, wo are these the New Kids on the Block? These guys?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;img src="http://1.bp.blogspot.com/_bXDgUoT7V7o/Sk4AeAT0K7I/AAAAAAAAANA/5lDV0iMS_V8/s400/newkids3.jpg" border="0" alt="" id="BLOGGER_PHOTO_ID_5354217522469284786" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 320px; height: 320px; " /&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;Nice hair! No, not them. Here are some of the New Kids on the Erlang Block:&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;div style="text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;img src="http://3.bp.blogspot.com/_bXDgUoT7V7o/Sk4EEAp6KfI/AAAAAAAAANo/dSLaROLb6oA/s400/collage.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5354221473931864562" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 332px; " /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;a href="http://heroku.com/"&gt;Heroku&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Heroku is a platform for deploying Ruby or Rails web apps. They've built their routing mesh in Erlang. This &lt;a href="http://heroku.com/how/architecture"&gt;routing mesh&lt;/a&gt; is used to load-balance and route requests to their application work units (dynos). This is a really cool piece of cloud infrastructure.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://www.hypernumbers.com/"&gt;HyperNumbers&lt;/a&gt;&lt;/div&gt;&lt;div&gt;HyperNumbers is a startup that is "doing for numbers what hypertext did for text". Think GoogleDocs, but each cell can be a resource that's located on another spreadsheet. Coming soon.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://couchdb.apache.org/"&gt;CouchDB&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://couchdb.apache.org/"&gt;&lt;/a&gt;CouchDB is a non-relational document database written in Erlang, and it is going from strength to strength. Look at this &lt;a href="http://www.google.com/trends?q=couchdb"&gt;graph&lt;/a&gt; from Google Trends:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;img src="http://3.bp.blogspot.com/_bXDgUoT7V7o/Sk4BZxgLNvI/AAAAAAAAANQ/y_IRGWiCzsA/s400/couchdd_trend.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 203px;" border="0" alt="" id="BLOGGER_PHOTO_ID_5354218549286745842" /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you don't know what CouchDB is, you should definitely have a look it. Even is it's just to make you challenge you preconceptions around databases, and relational ones in particular. There is also a &lt;a href="http://books.couchdb.org/relax/"&gt;book &lt;/a&gt;coming out soon. It's open source.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://www.rabbitmq.com/"&gt;RabbitMQ&lt;/a&gt;&lt;/div&gt;&lt;div&gt;RabbitMQ is an open source, high-performance enterprise messaging system, and an implementation of the messaging standard AQMP. It is written in Erlang. I've heard rumors that ActiveMQ (Java) has 20 times more lines of code. (NB: If anyone can give me a reference or link to proof I would be grateful). &lt;span class="Apple-style-span"  style="color:#FF0000;"&gt;Update: See comments for line counts.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://nitrogenproject.com/"&gt;Nitrogen Web Framework&lt;/a&gt;&lt;/div&gt;&lt;div&gt;This is my favourite web framework. It's a real Web 2.0 framework, and it's highly productive. Some of the &lt;a href="http://nitrogenproject.com/web/learn"&gt;main features&lt;/a&gt; are:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Event-Driven Development&lt;/li&gt;&lt;li&gt;Brainlessly Easy Ajax&lt;/li&gt;&lt;li&gt;Ridiculously Simple Comet&lt;/li&gt;&lt;li&gt;Complex Interfaces: Dragging, Dropping, and Sorting&lt;/li&gt;&lt;li&gt;Flexible Templating&lt;/li&gt;&lt;li&gt;Data Binding&lt;/li&gt;&lt;li&gt;Erlang Power&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;I've used this to create my own pet Erlang project, &lt;a href="http://www.opinion8r.com/"&gt;opinion8r.com&lt;/a&gt;. Check it out. It's also open source.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://www.smarkets.com/"&gt;Smarkets&lt;/a&gt;&lt;/div&gt;&lt;div&gt;I didn't go to this talk, but I've been to one given last year at the Erlang London User group. Smarkets is an online betting exchange which allows users to bet on anything they wish. It is implemented in Erlang and close to launch.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://campfirenow.com/"&gt;Campfire&lt;/a&gt;&lt;/div&gt;&lt;div&gt;Campfire is one of the applications from the guys at &lt;a href="http://www.37signals.com/"&gt;37Signals&lt;/a&gt;. If you don't know who 37 Signals are, they created Ruby on Rails. The functional C polling for Campfire service that was being used was replaced by an Erlang implementation:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;img src="http://1.bp.blogspot.com/_bXDgUoT7V7o/Sk4CNBqc-_I/AAAAAAAAANY/Phjz8_7XTfI/s400/campfire.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5354219429798149106" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 155px; " /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The Erlang implementation is as fast as the C implementation, modular and extensible, and much easier to administer with 1 OS process instead of 80.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://www.process-one.net/en/"&gt;ProcessOne&lt;/a&gt; - OneTeam Media Server&lt;/div&gt;&lt;div&gt;"OneTeam Media Server is a new project launched by ProcessOne which is a Media server for Flash clients implementing the FLV and RTMP protocol. The project is designed so that it can work hand in hand with ejabberd, ProcessOne Instant Messaging platform."&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This one is very exciting. The media server has Flash in the front end, and Erlang in the back. It makes thing like writing video chat in the browser ridiculously easy. And it's open source! Look out for it in July.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://code.google.com/p/webmachine/"&gt;Webmachine&lt;/a&gt;&lt;/div&gt;&lt;div&gt;Webmachine is the best way to implement a REST interface to your Erlang application that I've seen to date. I've &lt;a href="http://21ccw.blogspot.com/2008/06/migrating-native-erlang-interface-to.html"&gt;blogged&lt;/a&gt; about REST and Erlang in the past, but instead of rolling your own, you should look at this. Especially considering how it handles all the response codes correctly, does all the correct caching responses etc. Another open source project.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;And the point is...?&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I think it's a very positive indicator for the future of Erlang that it is being used in these new and exciting areas and companies. It is performing very well in the field of high-scalability infrastructure. It is well suited to Cloud computing. But most importantly, &lt;i&gt;&lt;b&gt;very clever and innovative people are using it and loving i&lt;/b&gt;&lt;b&gt;t&lt;/b&gt;&lt;/i&gt;. That, above all else, means the future looks good for Erlang.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;P.S. I'm sure I missed some great talks again this year, but lucky for me and you, you can find the talks and videos &lt;a href="http://www.erlang-factory.com/conference/London2009/talks"&gt;here&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-5669561164847906526?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/5669561164847906526/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=5669561164847906526' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/5669561164847906526'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/5669561164847906526'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2009/07/erlang-factory-2009-new-kids-on-erlang.html' title='Erlang Factory 2009 - New Kids on the Erlang Block'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_bXDgUoT7V7o/Sk4AeAT0K7I/AAAAAAAAANA/5lDV0iMS_V8/s72-c/newkids3.jpg' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-8818275353199237827</id><published>2009-06-05T15:50:00.000-07:00</published><updated>2009-06-05T16:02:29.895-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Google Wave'/><category scheme='http://www.blogger.com/atom/ns#' term='RIA'/><category scheme='http://www.blogger.com/atom/ns#' term='HTML5'/><title type='text'>Look out RIAs, HTML5 is coming</title><content type='html'>&lt;span style="font-family:sans-serif;"&gt;Google showing off &lt;a href="http://googlesystem.blogspot.com/2009/05/google-wave.html"&gt;Wave&lt;/a&gt;, has [re]ignited some discussion around HTML5. I read an article on InfoQ, &lt;a href="http://www.infoq.com/news/2009/06/Wave-Silverlight"&gt;Is Google Wave Going to Have an Impact on RIA/Silverlight?&lt;/a&gt; Which struck me as a bit of an odd question. Tim Heuer&lt;/span&gt;&lt;span style="font-family:sans-serif;"&gt; (in the Silverlight camp) is correct in asking  "&lt;a href="http://timheuer.com/blog/archive/2009/05/30/google-wave-forces-out-silverlight-flash-ria-platforms.aspx"&gt;is HTML5 really what people are talking about here?&lt;/a&gt;" and towards the end of the article, the correct question is polled:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;"What negative impact will HTML 5/Wave going to have on RIA/Silverlight?" [sic]&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;At the time the results of the poll were:&lt;br /&gt;&lt;br /&gt;Major: 66&lt;br /&gt;Significant: 115&lt;br /&gt;Small: 112&lt;br /&gt;None: 43&lt;br /&gt;Total votes: 336&lt;br /&gt;&lt;br /&gt;I was a bit surprised by the poll results. I voted "Major", and I find it hard to understand how someone could vote "None". None?! Really? I do realise that this is partly due to the fact that most people, including me, think they are above average (I just discovered that it is also called the &lt;a href="http://en.wikipedia.org/wiki/Lake_Wobegon_effect"&gt;Lake Wobegon&lt;/a&gt; effect. Cute). There is also some confusion about including Wave in the question, which has nothing to do with the issue. I'll try and convince you of the potential impact...&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;Why RIAs exist&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The existence of &lt;a href="http://en.wikipedia.org/wiki/Rich_Internet_application"&gt;Rich Internet Applications&lt;/a&gt; can be explained by the word "Rich" in the name. At some point in the past few years, people started to realise that HTML didn't keep up with the explosion in bandwidth and processing power available to users, and that the web experience could be "richer". This was especially obvious in areas such as not having the ability to to arbitrary on-screen animantions and rendering on a "canvas", the ability to store data locally on user's hard drives and drag-and-drop, amongst others. The lack of these features in HTML created a gap in the market, but only whilst browser technology continues to lag. This gap has been exploited by Flex, Silverlight and JavaFX.&lt;br /&gt;&lt;br /&gt;&lt;b style="font-style: italic;"&gt;This situation is changing.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Enter HTML5&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Some of the features in HTML5 will add some really exciting possibilities to applications running on the browser platform:&lt;br /&gt;&lt;span style="font-size:100%;"&gt;&lt;br /&gt;&lt;b&gt;Video&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Although I wouldn't categorise video as a major driver for RIA technologies, it is important to mention in relation to Adobe flash, which is the dominant technology for delivering internet video. YouTube is already delivering non-flash internet video in mobile devices. The introduction of the &lt;video&gt;&lt;/video&gt; tag will reduce the pressure of deploying flash on every platform, which could have an effect on the usage of Flex. Consider that Flash is not available on the iPhone: if you have a choice of developing your application on just one technology (HTML5) for both desktop and mobile, or choosing 2, which would cost less?&lt;br /&gt;&lt;span style="font-size:100%;"&gt;&lt;br /&gt;&lt;b&gt;Canvas drawing (immediate mode drawing)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Canvas drawing will enable application developers to fill a gaping hole in native browser capabilities - gaming. The gaming industry is bigger than the movie industry, and the availability of a virtually universally available standard platform will not got unnoticed by game developers. Of course this capability doesn't only apply to gaming.&lt;br /&gt;&lt;div style="font-weight: bold;"&gt;&lt;span style="font-size:100%;"&gt;&lt;br /&gt;Local storage&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span style="font-size:100%;"&gt;&lt;b&gt;&lt;span style="font-weight: normal;"&gt;As long as there's no capability for storing data offline, web applications continue to be considered the poor cousin of desktop applications. Google documents are not availble offline without &lt;a href="http://gears.google.com/"&gt;Google Gears&lt;/a&gt;. Having local storage available in a &lt;/span&gt;&lt;i&gt;&lt;span style="font-weight: normal;"&gt;standardised&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-weight: normal;"&gt;&lt;span style="font-size:100%;"&gt; way is a big attraction for application developers. Gears might work for Gooogle, but not everyone is so keen on having a dependency on a non-standard technology.&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Some other disavantages to RIAs which could favour HTML5 applications&lt;/span&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;- RIAs are usually everything or nothing. They don't play well with embedding RIA functionality into existing web pages. HTML5 allows you to grow your application, creating a richer and richer experience as you go along.&lt;br /&gt;- Some RIAs have proprietary technologies.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;- RIAs require an extra &lt;b&gt;deployment&lt;/b&gt; step on top of deploying the browser. Browsers implementing HTML5 will provide a more standardised way of deploying applications to users, also in the corporate environment where locked-down systems are common. Choosing a technology that doesn't require another installation step is a big attraction for corporate IT departments.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;In the long term&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There another significant point to make.&lt;br /&gt;&lt;br /&gt;I believe it is part of Google's long term strategy to make the browser the de-facto application platform. &lt;b&gt;To displace Windows as the dominant aplication platform&lt;/b&gt;. You can see it already with Chrome, how the tabs on the browser have become the new taskbar, and the integration of browser tabs as "applications" into the host OS. For example, features like local file storage and the canvas provide a path for Google Docs to become more like MS Office. You can be sure that some of the drive behind the HTML5 standard is coming from the Google docs team. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I'm not the only one thinking along these lines: &lt;a href="http://industry.bnet.com/technology/10002039/googles-long-shot-at-kicking-microsoft-off-the-desktop/"&gt;http://industry.bnet.com/technology/10002039/googles-long-shot-at-kicking-microsoft-off-the-desktop/&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;In closing&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I'm not trying to say that HTML5 will eliminate RIA technologies, but I do believe that thinking that its impact on RIA will be "small" or "none" is absolutely ridiculous.&lt;br /&gt;&lt;br /&gt;There are some big players pushing the browser to become the dominant application platform. HTML5 is a big step in that direction.&lt;br /&gt;&lt;br /&gt;If you disagree, I look forward to your comments!&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-8818275353199237827?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/8818275353199237827/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=8818275353199237827' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/8818275353199237827'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/8818275353199237827'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2009/06/look-out-rias-html5-is-coming.html' title='Look out RIAs, HTML5 is coming'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-1553935460548581277</id><published>2009-05-20T11:29:00.000-07:00</published><updated>2009-05-20T13:00:31.575-07:00</updated><title type='text'>The Law of Conservation of Misery</title><content type='html'>At university I had a professor who would jokingly refer to his Law of Conservation of Misery:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;The total amount of misery in a system is constant&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;which implies that when you try and decrease the misery in one aspect of a system, you will increase the misery in some of the other aspects.&lt;br /&gt;&lt;br /&gt;There are many types of misery. In software engineering projects these could include non-maintainability, non-performance and budget constraints.&lt;br /&gt;&lt;br /&gt;Personally, I think people are too prone to think that there are &lt;a href="http://en.wikipedia.org/wiki/Zero-sum"&gt;zero-sum&lt;/a&gt; monsters lurking under each bed, so my own take on the "law" would be:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;The total amount of misery in a system is &gt; 0&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;&lt;/span&gt;&lt;/span&gt;Which implies that you &lt;span style="font-weight: bold;"&gt;can&lt;/span&gt; make tradeoffs that decrease the total amount of misery&lt;span style="font-style: italic;"&gt;, &lt;/span&gt;but you can never eliminate it completely. A bad implementation of a system will have a higher total amount of misery, and a good one will have less, but you will always have at least a little bit!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-1553935460548581277?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/1553935460548581277/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=1553935460548581277' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/1553935460548581277'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/1553935460548581277'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2009/05/law-of-conservation-of-misery.html' title='The Law of Conservation of Misery'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-856792514420372731</id><published>2009-05-19T11:19:00.000-07:00</published><updated>2009-05-19T11:42:07.050-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='smtp'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>How to send email via Gmail using Erlang</title><content type='html'>One of my pet projects, &lt;a href="http://www.dayfindr.com"&gt;www.dayfindr.com&lt;/a&gt;, integrates with email to send notifications to users.&lt;br /&gt;&lt;br /&gt;I use &lt;a href="http://www.blogger.com/www.google.com/apps%20%20"&gt;Google Apps&lt;/a&gt; for email infrastructure, so you need an SMTP client that supports &lt;a href="http://en.wikipedia.org/wiki/Secure_Sockets_Layer"&gt;TLS&lt;/a&gt;. At the time, I couldn't find a simple Erlang SMTP client that could handle TLS, so I used a command-line SMTP client.&lt;br /&gt;&lt;br /&gt;For my new pet project, for want of a better name temporarily called  &lt;a href="http://www.theisabelleproject.com/"&gt;The Isabelle Project&lt;/a&gt;, I need to add some email functionality. This time I would prefer to use an Erlang solution with proper error handling and logging.&lt;br /&gt;&lt;br /&gt;I looked at the &lt;a href="http://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol"&gt;SMTP&lt;/a&gt; protocol on Wikipedia, and it didn't seem to difficult. Erlang's built-in ssl module also seemed to support TLS. So, with a bit of trial and error, here's the result:&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;-module(smtp).&lt;br /&gt;-export([connect/0]).&lt;br /&gt;&lt;br /&gt;connect() -&gt;&lt;br /&gt;    {ok, Socket} = ssl:connect("smtp.gmail.com", 465, [{active, false}], 1000),&lt;br /&gt;    recv(Socket),&lt;br /&gt;    send(Socket, "HELO localhost"),&lt;br /&gt;    send(Socket, "AUTH LOGIN"),&lt;br /&gt;    send(Socket, binary_to_list(base64:encode("___@gmail.com"))),&lt;br /&gt;    send(Socket, binary_to_list(base64:encode("johngalt"))),&lt;br /&gt;    send(Socket, "MAIL FROM: &lt;___@gmail.com&gt;"),&lt;br /&gt;    send(Socket, "RCPT TO:&lt;___@gmail.com&gt;"),&lt;br /&gt;    send(Socket, "DATA"),&lt;br /&gt;    send_no_receive(Socket, "From: &lt;___@gmail.com&gt;"),&lt;br /&gt;    send_no_receive(Socket, "To: &lt;___@gmail.com&gt;"),&lt;br /&gt;    send_no_receive(Socket, "Date: Tue, 15 Jan 2008 16:02:43 +0000"),&lt;br /&gt;    send_no_receive(Socket, "Subject: Test message"),&lt;br /&gt;    send_no_receive(Socket, ""),&lt;br /&gt;    send_no_receive(Socket, "This is a test"),&lt;br /&gt;    send_no_receive(Socket, ""),&lt;br /&gt;    send(Socket, "."),&lt;br /&gt;    send(Socket, "QUIT"),&lt;br /&gt;    ssl:close(Socket).&lt;br /&gt;&lt;br /&gt;send_no_receive(Socket, Data) -&gt;&lt;br /&gt;    ssl:send(Socket, Data ++ "\r\n").&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;send(Socket, Data) -&gt;&lt;br /&gt;    ssl:send(Socket, Data ++ "\r\n"),&lt;br /&gt;    recv(Socket).&lt;br /&gt;&lt;br /&gt;recv(Socket) -&gt;&lt;br /&gt;    case ssl:recv(Socket, 0, 1000) of&lt;br /&gt; {ok, Return} -&gt; io:format("~p~n", [Return]);&lt;br /&gt; {error, Reason} -&gt; io:format("ERROR: ~p~n", [Reason])&lt;br /&gt;    end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And the output from the Erlang shell:&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;3&gt; application:start(ssl).&lt;br /&gt;ok&lt;br /&gt;4&gt; smtp:connect().&lt;br /&gt;"220 mx.google.com ESMTP y37sm613282mug.19\r\n"&lt;br /&gt;"250 mx.google.com at your service\r\n"&lt;br /&gt;"334 VXNlcm5hbWU6\r\n"&lt;br /&gt;"334 UGFzc3dvcmQ6\r\n"&lt;br /&gt;"235 2.7.0 Accepted\r\n"&lt;br /&gt;"250 2.1.0 OK y37sm613282mug.19\r\n"&lt;br /&gt;"250 2.1.5 OK y37sm613282mug.19\r\n"&lt;br /&gt;"354  Go ahead y37sm613282mug.19\r\n"&lt;br /&gt;"250 2.0.0 OK 1242683885 y37sm613282mug.19\r\n"&lt;br /&gt;"221 2.0.0 closing connection y37sm613282mug.19\r\n"&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The only tricky bit is that for the AUTO LOGIN, the received text and the username and password you send is base-64 encoded. By default the connect is active=false, which means the responses are send to the creating process directly. Using passive mode requires explicit receiving of the response using ssl:recv/2&lt;br /&gt;&lt;br /&gt;You'll have to handle errors better if you use this in production, but the basic protocol is pretty straightforward...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-856792514420372731?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/856792514420372731/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=856792514420372731' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/856792514420372731'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/856792514420372731'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2009/05/how-to-send-email-via-gmail-using.html' title='How to send email via Gmail using Erlang'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-7203639609804814726</id><published>2009-01-02T03:31:00.000-08:00</published><updated>2009-01-06T04:16:50.140-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Object Oriented Programming'/><title type='text'>Is a Square a Rectangle?</title><content type='html'>I pre-ordered the &lt;a href="http://clojure.org/"&gt;Clojure&lt;/a&gt; &lt;a href="http://www.pragprog.com/titles/shcloj/programming-clojure"&gt;book&lt;/a&gt; today, so I downloaded Clojure and had a peek at something that I know is interesting - "multimethods". I came across the following example statement:&lt;br /&gt;&lt;br /&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;(derive ::square ::rect)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;i.e. a Square IS-A Rectangle in the Object-Oriented sense.&lt;span class="Apple-style-span" style="font-style: italic;"&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-style: italic;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;This rang a bell for me. Not a nice melodious one, but one that's a little off-key. A good teacher asked some colleagues and me the question "Is a Square a Rectangle?" a few years ago, and the answer wasn't all that clear ti us. But he showed us a realitively easy way to determine to some extent the validity of any IS-A relationship.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I will try and show you this principle by way of actually designing this hierarchy. First we design a simple Rectangle in Ruby:&lt;/div&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;class RectangleTest &amp;lt; Test::Unit::TestCase&lt;br /&gt;  def test_width_height&lt;br /&gt;&lt;br /&gt;    # Given&lt;br /&gt;    r = Rectangle.new(1,1)&lt;br /&gt;&lt;br /&gt;    # Then&lt;br /&gt;    assert_equal 1, r.width&lt;br /&gt;    assert_equal 1, r.height&lt;br /&gt;&lt;br /&gt;    # When&lt;br /&gt;    r.width = 5&lt;br /&gt;&lt;br /&gt;    # Then&lt;br /&gt;    assert_equal 5, r.width&lt;br /&gt;    assert_equal 1, r.height&lt;br /&gt;&lt;br /&gt;    # When&lt;br /&gt;    r.height = 2&lt;br /&gt;    &lt;br /&gt;    # Then&lt;br /&gt;    assert_equal 5, r.width&lt;br /&gt;    assert_equal 2, r.height&lt;br /&gt;&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;If you think it's strange that I use a test as a design, I suggest you read &lt;a href="http://en.wikipedia.org/wiki/Test-driven_development"&gt;this&lt;/a&gt;. I'm not even going to show the implementation since it's so simple. Next we will design our Square class:&lt;/div&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;class SquareTest &amp;lt; Test::Unit::TestCase&lt;br /&gt;  def test_width_height&lt;br /&gt;&lt;br /&gt;    # Given&lt;br /&gt;    s = Square.new(3)&lt;br /&gt;&lt;br /&gt;    # Then&lt;br /&gt;    assert_equal 3, s.width&lt;br /&gt;    assert_equal 3, s.height&lt;br /&gt;&lt;br /&gt;    # When&lt;br /&gt;    s.width = 5&lt;br /&gt;&lt;br /&gt;    # Then&lt;br /&gt;    assert_equal 5, s.width&lt;br /&gt;    assert_equal 5, s.height&lt;br /&gt;&lt;br /&gt;    # When&lt;br /&gt;    s.height = 2&lt;br /&gt;    &lt;br /&gt;    # Then&lt;br /&gt;    assert_equal 2, s.width&lt;br /&gt;    assert_equal 2, s.height&lt;br /&gt;&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;And you can see that we make sure that when either the width or height has been set, we get the same value for both the width and height respectively. The same goes for the single-parameter constructor. Again, the implementation is trivial. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Now, you might think that this design is &lt;span class="Apple-style-span" style="font-style: italic;"&gt;OK&lt;/span&gt;, but it's not. We have to ensure that our inheritance doesn't break the &lt;a href="http://en.wikipedia.org/wiki/Liskov_substitution_principle"&gt;Liskov Substritution Principle.&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;From Wikipedia:&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-style: italic; "&gt;"Liskov's notion of 'subtype' is based on the notion of substitutability; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness)."&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-style: italic;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;This means that for our designs, the unit test for the Rectangle should pass when I substitute the Rectangle instance with a Square instance. Will it? No it won't. Because in the rectangle test, we ensure that when the width is set, the height is not affected, and vice versa. Using a Square instance here will break the test.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Another way of looking at it is to think of the contract of the setters, and the pre and post conditions of those contracts. According to the Liskov princple, (again, from Wikipedia):&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="font-style: italic;"&gt;Preconditions cannot be strengthened in a subclass.&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="font-style: italic;"&gt;Postconditions cannot be weakened in a subclass.&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;If we design the Square to force the width to be set when setting the height (and vice versa), we are &lt;span class="Apple-style-span" style="font-style: italic;"&gt;weakening&lt;/span&gt; the postconditions of the Rectangle methods. I.e. the post condition that states that either the width or height should &lt;span class="Apple-style-span" style="font-style: italic;"&gt;not&lt;/span&gt; be altered when setting the height or width respectively has been weakened.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In conclusion, here we have a Square that is NOT a Rectangle. The lesson to be learnt is that (unfortunately for us), IS-A in the real world or other domains does not necessarily imply IS-A in the OO world! &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;P.S. If you remove the setters from the Rectangle, and only allow the fields to be set during construction, there is no violation of the Liskov Substitution Principle :)&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-7203639609804814726?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/7203639609804814726/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=7203639609804814726' title='14 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/7203639609804814726'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/7203639609804814726'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2009/01/is-square-rectangle.html' title='Is a Square a Rectangle?'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>14</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-8710392401372419187</id><published>2008-11-12T01:07:00.000-08:00</published><updated>2008-11-18T14:00:19.438-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Keynote'/><category scheme='http://www.blogger.com/atom/ns#' term='Presentations'/><category scheme='http://www.blogger.com/atom/ns#' term='Powerpoint'/><title type='text'>Does Robert Mugabe use Powerpoint?</title><content type='html'>I think most people will agree that &lt;a href="http://en.wikipedia.org/wiki/Robert_Mugabe"&gt;Robert Mugabe&lt;/a&gt; is a terrible dictator who has scant disregard for the people he is supposed to be serving. The same goes for the likes of Stalin, Hitler, Idi Amin etc. That being said, I think most people will also agree that one of the reasons of their rise to power was specifically their ability to influence people. In other words&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-style: italic;"&gt;People listen[ed] to them, and they're not using Powerpoint&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;My experience is that every time someone want to &lt;span class="Apple-style-span" style="font-style: italic;"&gt;say something&lt;/span&gt; to more than one person, there has to be a Powerpoint presentation to go along with it. And more often than not, this attempt at communication fails miserably, or succeeds only in some rudimentary fashion. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Some of the causes of these failures are:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;People are reading the slides, so they're not listening to the speaker.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;The presenter is reading the slides, so there is no connection with the audience.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;The bullet-pointed slides are so mind-numbingly &lt;span class="Apple-style-span" style="font-style: italic;"&gt;boring&lt;/span&gt; that the audience members have gone to their happy place or are thinking of what to cook/order/kill for dinner and of course, they're &lt;span class="Apple-style-span" style="font-style: italic;"&gt;not listening.&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;In contrast, I'll mention two very successful entrepeneurs and their presentation styles: &lt;a href="http://en.wikipedia.org/wiki/Steve_Jobs"&gt;Steve Jobs&lt;/a&gt; (Apple, Pixar, &lt;a href="http://www.nytimes.com/2008/11/12/opinion/12friedman.html?_r=1&amp;amp;partner=permalink&amp;amp;exprod=permalink&amp;amp;oref=slogin"&gt;General Motors?&lt;/a&gt;) and &lt;a href="http://en.wikipedia.org/wiki/Elon_Musk"&gt;Elon Musk&lt;/a&gt; (PayPal, Tesla Motors, SpaceX). In business it is absolutely essential that you can effectively communicate your ideas to people. And when you audience is holding the purse strings, and you can't convince them of your business ideas, you're doomed to become a (perhaps very successful) hobbyist.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I went to a talk by Elon Musk on his company &lt;a href="http://www.spacex.com/"&gt;SpaceX&lt;/a&gt; a few months ago. The projector had been set up and the presentation was ready to proceed. The first slide was already on the screen, with text along the lines of "Elon Musk, SpaceX" with very nice picture of a rocket (on a Pacific island if I remember correctly). &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Elon Musk came in and talked a bit. After a few minutes he proceeded to questions, and these went on for more than an hour. The first slide was the &lt;span class="Apple-style-span" style="font-style: italic;"&gt;only&lt;/span&gt; slide. Maybe the host created it when he realised there were no slides. &lt;span class="Apple-style-span" style="font-style: italic;"&gt;"Are you crazy? &lt;/span&gt;&lt;span class="Apple-style-span" style=""&gt;&lt;span class="Apple-style-span" style="font-style: italic;"&gt;No slides?! But how will you&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-style: italic;"&gt;...."&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The talk was very effective, he had communicated his points, and gave the audience exactly what they wanted, since they were asking him what they wanted to know!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The second example is Steve Jobs. I remember watching the first iPhone Keynote speech and how I got swept up in the presentation. It was a highly effective communication (helped by a great product). Let's look at some of the slides he used:&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;img src="http://1.bp.blogspot.com/_bXDgUoT7V7o/SRws-5svzNI/AAAAAAAAAIM/WwqN3676wwM/s400/dsc_0236.jpg" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 265px;" border="0" alt="" id="BLOGGER_PHOTO_ID_5268135123268652242" /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_bXDgUoT7V7o/SRwtIIPj6FI/AAAAAAAAAIU/2bn3R_0PMxo/s1600-h/dsc_0245.jpg"&gt;&lt;br /&gt;&lt;br /&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 265px;" src="http://2.bp.blogspot.com/_bXDgUoT7V7o/SRwtIIPj6FI/AAAAAAAAAIU/2bn3R_0PMxo/s400/dsc_0245.jpg" border="0" alt="" id="BLOGGER_PHOTO_ID_5268135281791592530" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;img src="http://1.bp.blogspot.com/_bXDgUoT7V7o/SRwtNIvaHWI/AAAAAAAAAIc/uURI3AxDKOE/s400/dsc_0175.jpg" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 265px;" border="0" alt="" id="BLOGGER_PHOTO_ID_5268135367824514402" /&gt;&lt;br /&gt;Do you see lots of bullet points? The last slide has the most text on it, but that slide is in the minority, and the bullets are actually very short. The first two slides had simple images, and minimal text, and I can assure you that people were &lt;span class="Apple-style-span" style="font-style: italic;"&gt;listening&lt;/span&gt; to what he was saying!&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Do you think the response would have been the same if his slides looked like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_bXDgUoT7V7o/SRwuDpzhL7I/AAAAAAAAAIk/aTBoI-Ic_Uc/s1600-h/boring.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 282px;" src="http://2.bp.blogspot.com/_bXDgUoT7V7o/SRwuDpzhL7I/AAAAAAAAAIk/aTBoI-Ic_Uc/s400/boring.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5268136304413061042" /&gt;&lt;/a&gt;&lt;div&gt;instead? I'm asleep already.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;My highly opinionated advice for presentations would be:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Don't use PowerPoint/Keynote/[insert slide software here] to write your presentation. Decide what you want to say and how you want it to flow and only prepare your slides once you know the content of your presentation. &lt;br /&gt;&lt;/li&gt;&lt;li&gt;Don't use your slides as notes, have seperate notes if you don't know the topic that well or need some backup information.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Don't read you slides.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Don't have lots of bullet points on each slide.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: rgb(255, 0, 0);"&gt;You don't &lt;/span&gt;&lt;span class="Apple-style-span" style="font-style: italic; "&gt;&lt;span class="Apple-style-span" style="color: rgb(255, 0, 0);"&gt;need&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: rgb(255, 0, 0);"&gt; to use slides.&lt;/span&gt; Sometimes a white board is good enough, and in most cases it is probably better.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;What do you think?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-8710392401372419187?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/8710392401372419187/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=8710392401372419187' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/8710392401372419187'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/8710392401372419187'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/11/does-robert-mugabe-use-powerpoint.html' title='Does Robert Mugabe use Powerpoint?'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_bXDgUoT7V7o/SRws-5svzNI/AAAAAAAAAIM/WwqN3676wwM/s72-c/dsc_0236.jpg' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-5384711924776737130</id><published>2008-09-08T05:31:00.000-07:00</published><updated>2008-10-01T04:38:07.474-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Software Development Process'/><category scheme='http://www.blogger.com/atom/ns#' term='Agile Development'/><category scheme='http://www.blogger.com/atom/ns#' term='Iterative Development'/><title type='text'>Iterative and Agile - the Venn Diagram of truth</title><content type='html'>&lt;div&gt;I'm seeing a bit of this lately:&lt;span class="Apple-tab-span" style="white-space:pre"&gt; &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-style: italic;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-style: italic;"&gt;"Iterative software development is Agile software development."&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-style: italic;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;and I have to say that I disagree quite strongly. The Venn diagram actually looks more like this:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;img src="http://1.bp.blogspot.com/_bXDgUoT7V7o/SOI-BbG_ZmI/AAAAAAAAAIE/DfiQYsrWfI0/s400/agile_venn.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" border="0" alt="" id="BLOGGER_PHOTO_ID_5251828309645289058" /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This post has been prompted by two things. First, a few weeks ago a SCRUMmer made some comments along the following lines:&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="font-style: italic; "&gt;"We don't allow changing the iteration plan once the iteration has begun" &lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="font-style: italic; "&gt;"All the requirements for the stories for the iteration must have been gathered before it starts."&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="font-style: italic; "&gt;"If the customer want something else, they have to wait for the next iteration"&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;And some &lt;a href="http://www.infoq.com/news/2008/09/Short-Iterations-Mishkin-Berteig"&gt;articles&lt;/a&gt;/&lt;a href="http://agile-commentary.blogspot.com/2008/09/shorten-your-iteration.html"&gt;blogs&lt;/a&gt; about iteration lengths that jogged my memory about what I disagreed with. The root of my discomfort in equating iterative development to agile development is because&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-style: italic;"&gt;agile software development is a set of values, it is not a process. &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In fact, one of the core values of Agile development is to &lt;span class="Apple-style-span" style="font-style: italic;"&gt;not&lt;/span&gt; value you process too much. There are some processes that try to embody Agile values, and they have different interpretations and practicalities around those values, but these &lt;span class="Apple-style-span" style="font-style: italic;"&gt;processes&lt;/span&gt; are not what defines &lt;span class="Apple-style-span" style="font-style: italic;"&gt;Agile&lt;/span&gt; development. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here's a &lt;a href="http://agilemanifesto.org/"&gt;reminder&lt;/a&gt; of what it means to be agile:&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;Individuals and interactions&lt;/span&gt; over processes and tools &lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;Working software&lt;/span&gt; over comprehensive documentation &lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;Customer collaboration&lt;/span&gt; over contract negotiation &lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;Responding to change&lt;/span&gt; over following a plan &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;It's very possible to do iterative development without embodying &lt;span class="Apple-style-span" style="font-style: italic;"&gt;any&lt;/span&gt; of these values. Treating each iteration like a waterfall with the usual waterfall suspects (requirements up front, little room for manoevering, opressive specification and documentation) is a case in point. And to me, valuing your plan over your customer by not responding to their needs is &lt;span class="Apple-style-span" style="font-style: italic;"&gt;not&lt;/span&gt; Agile.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You are, of course, invited to disagree...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-5384711924776737130?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/5384711924776737130/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=5384711924776737130' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/5384711924776737130'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/5384711924776737130'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/09/iterative-and-agile-venn-diagram-of.html' title='Iterative and Agile - the Venn Diagram of truth'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_bXDgUoT7V7o/SOI-BbG_ZmI/AAAAAAAAAIE/DfiQYsrWfI0/s72-c/agile_venn.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-4962382545561174872</id><published>2008-09-03T09:29:00.000-07:00</published><updated>2008-09-08T05:08:51.419-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Application Platform'/><category scheme='http://www.blogger.com/atom/ns#' term='DHTML'/><category scheme='http://www.blogger.com/atom/ns#' term='Google Chrome'/><title type='text'>Me and my dog on Google Chrome</title><content type='html'>Like every man and his dog I feel compelled to say something about &lt;a href="http://www.google.com/chrome"&gt;Chrome&lt;/a&gt;, Google's new browser. Most impressions I have read have been favourable, and my personal experience so far is very positive. Most reviews I have scanned have been thumbs-up, and have focussed on the usual suspects, i.e. rendering and javascript performance, stability, usability etc. etc.&lt;div style="text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For me, there is something subtle and extremely powerful that comes with Chrome. It fields a combination of features with very powerful implications. A "whole being more that the sum of the parts" kind of situation.&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In the past year or two there has been a lot of focus in the "richer" web experience, and on ways of getting the web to feel more like the desktop. Newish technologies like Flex and Silverlight have received significant attention, and DHTML (Javascript+CSS+DOM+HTML) seems to have become the cousin that you are obliged to invite to your wedding party (or application party), but don't really want to. I believe that part of the reason for this has been Javascript performance, but there are several efforts underway (e.g. TraceMonkey and now Chrome's V8 Javascript engine) that will bring huge leaps in performance to Javascript. Also, I prefer to support non-proprietary technologies such as DHTML over vendor-based alternatives like Flex and Silverlight.&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The second feature that's important in Chrome is process isolation for tabs. This enables each tab to be completely isolated from other processes. One tab going down doesn't take the whole browser with it (which is something that has plagued my Firefox quite a bit).&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Thirdly, we have "application-like" OS integration in Chrome. Chrome has the ability to make a single tab look like an application window, with normal window decorations:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;img src="http://3.bp.blogspot.com/_bXDgUoT7V7o/SMUU8kz1zyI/AAAAAAAAAH8/B0hxkM8SvUQ/s400/titlebar.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" border="0" alt="" id="BLOGGER_PHOTO_ID_5243620372048629538" /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In your taskbar it looks like an application, there is  quick lauch icon for you app:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;img src="http://4.bp.blogspot.com/_bXDgUoT7V7o/SMUTlKE0ksI/AAAAAAAAAHs/P4OOCC6YjoI/s400/taskbar.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" border="0" alt="" id="BLOGGER_PHOTO_ID_5243618870223475394" /&gt;There's also a Start Menu item (not shown) and a Desktop icon:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;img src="http://3.bp.blogspot.com/_bXDgUoT7V7o/SMUT16R0EhI/AAAAAAAAAH0/pMHvbHYvCzI/s400/shortcut.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" border="0" alt="" id="BLOGGER_PHOTO_ID_5243619158040777234" /&gt;&lt;div&gt;&lt;br /&gt;The combination of these three is a significant step towards blurring the lines between the desktop and the web. Chrome is an application platform with (potentially) desktop-like application performance (DHTML + V8), process isolation (a key feaure of an application platform) and the web apps look like desktop apps (Operating system integration). And it's all standards based.&lt;br /&gt;&lt;br /&gt;There have been some rumours of a Google OS...&lt;br /&gt;&lt;br /&gt;You're looking at it.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-4962382545561174872?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/4962382545561174872/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=4962382545561174872' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/4962382545561174872'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/4962382545561174872'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/09/me-and-my-dog-on-google-chrome.html' title='Me and my dog on Google Chrome'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_bXDgUoT7V7o/SMUU8kz1zyI/AAAAAAAAAH8/B0hxkM8SvUQ/s72-c/titlebar.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-1549559424813718640</id><published>2008-07-24T04:46:00.000-07:00</published><updated>2008-07-24T05:02:34.724-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scalaris'/><category scheme='http://www.blogger.com/atom/ns#' term='Distributed databases'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Scalaris Released</title><content type='html'>If you were at the &lt;a href="http://www.erlang-exchange.com/"&gt;Erlang Exchange&lt;/a&gt; in London last month, you should know that one of the hottest talks was given by &lt;a href="http://erlang-exchange.com/alexander-reinefeld"&gt;Alexander Reinefeld&lt;/a&gt;, "&lt;a href="http://skillsmatter.com/podcast/erlang/building-a-transactional-distributed-data-store-with-erlang"&gt;Building a transactional distributed data store with Erlang&lt;/a&gt;"&lt;br /&gt;&lt;br /&gt;Joe Armstrong seems to like it too:&lt;br /&gt;&lt;br /&gt;"I might be wrong, but my gut feeling is that what Alexander Reinefeld showed us will be the first killer application in Erlang."&lt;br /&gt;&lt;br /&gt;"So my take on this is that this is one of the sexiest applications I've seen in many a year. I've been waiting for this to happen for a long while. The work is backed by quadzillion Ph.D's and is really good &lt;span style="font-style: italic;"&gt;believe me."&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;- http://armstrongonsoftware.blogspot.com/2008/06/itching-my-programming-nerve.html&lt;br /&gt;&lt;br /&gt;Well, seems like Scalaris has been released! The link went up on the website in the last 24-48 hours. I haven't had the time to look at it yet, but you can grab it &lt;a href="http://code.google.com/p/scalaris/"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-1549559424813718640?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/1549559424813718640/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=1549559424813718640' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/1549559424813718640'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/1549559424813718640'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/07/scalaris-released.html' title='Scalaris Released'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-2790488996638110334</id><published>2008-07-02T04:09:00.000-07:00</published><updated>2008-07-02T05:31:19.996-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='OTP'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang exchange'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Notes from Erlang Exchange</title><content type='html'>I was at the &lt;a href="http://www.erlang-exchange.com/"&gt;Erlang Exchange&lt;/a&gt; in London last week, which I really enjoyed. It was really great to see &lt;a href="http://erlang-exchange.com/joe-armstrong"&gt;Joe Armstrong&lt;/a&gt;, and to see the faces behind the projects, e.g. &lt;a href="http://erlang-exchange.com/klacke-wikstrom"&gt;Claes Wikström&lt;/a&gt; (Yaws, Bluetail, &lt;a href="http://www.kreditor.se/"&gt;Kreditor&lt;/a&gt;, &lt;a href="http://www.tail-f.com/"&gt;Tail-f&lt;/a&gt;), &lt;a href="http://erlang-exchange.com/steve-vinoski"&gt;Steve Vinoski&lt;/a&gt;,  &lt;a href="http://erlang-exchange.com/eric-merrit-and-martin-logan"&gt;Eric Merrit&lt;/a&gt; and &lt;a href="http://erlang-exchange.com/eric-merrit-and-martin-logan"&gt;Martin Logan&lt;/a&gt; (&lt;a href="http://www.erlware.org/"&gt;Erlware&lt;/a&gt;), &lt;a href="http://erlang-exchange.com/matthias-radestock-and-tony-garnock-jones"&gt;Matthias Radestock&lt;/a&gt; (&lt;a href="http://www.lshift.net/"&gt;LShift &lt;/a&gt;&amp;amp; &lt;a href="http://www.rabbitmq.com/"&gt;RabbitMQ&lt;/a&gt;), &lt;a href="http://www.erlang-exchange.com/mickael-remond"&gt;Mickaël Rémond&lt;/a&gt; (&lt;a href="http://www.ejabberd.im/"&gt;ejabberd&lt;/a&gt;) etc. etc.&lt;br /&gt;&lt;br /&gt;Something that Joe said reminded me of a comment that &lt;a href="http://peripateticaxiom.blogspot.com/"&gt;Keith&lt;/a&gt; made about 6 months ago. I think this is something that gets lost in the hype around Erlang and multicore computing:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Erlang was designed to program fault tolerant systems. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;It was not designed to program multicore computers. It was designed 15-20 years ago when multicore-everywhere was not even on the horizon. As a side-effect, it is a good language for multicore systems, since the only way to actually &lt;span style="font-style: italic;"&gt;achieve&lt;/span&gt; fault tolerance is to have &lt;span style="font-style: italic;"&gt;independant&lt;/span&gt; processes (read &lt;a href="http://ml.osdir.com/lang.erlang.general/2003-04/msg00350.html"&gt;here&lt;/a&gt; for some more on this).&lt;br /&gt;&lt;br /&gt;I think it's important for us as to remember to advocate this, and instead of marketing Erlang/OTP as "the application system for multicore", we should be marketing it as "the application system for fault tolerance". Oh, and by the way "this is one of the few systems that will actually use those cores that are coming your way".&lt;br /&gt;&lt;br /&gt;The second point that I have been pondering, was brought on my &lt;a href="http://erlang-exchange.com/gordon-guthrie"&gt;Gordon Guthrie&lt;/a&gt;'s talk about Erlang/OTP vs Google Apps as an application engine. At this moment, there aren't many application systems to choose from, Erlang/OTP, Google Apps and Amazon EC2 come to mind. In the context of the talk, an Application System (A/S) would be something that takes care of a lot of the non-functional requirements of you system, e.g. reliability, scalability (+distribution) etc.&lt;br /&gt;&lt;br /&gt;For some businesses, using Google Apps or EC2 would be a good fit, but then you're tied into the value chain of that business, and in some instances you just cannot (for legal and other reasons) put your data on servers you don't own. If you go the OTP route you have full control over your application(s), but there's a lot of extra infrastructure that you will have to supply yourself.&lt;br /&gt;&lt;br /&gt;If you start thinking of Erlang/OTP in this way, you realise that the terms should be reversed and it should actually be OTP/Erlang. The platform is actually the important thing. The &lt;span style="font-style: italic;"&gt;platform&lt;/span&gt; is the thing that gives you the reliability, scalability, distribution and hot code swapping etc. that you require. Erlang becomes this awesome language that you use to build things on the &lt;span style="font-style: italic;"&gt;platform&lt;/span&gt;. Looking at things from a different perspective can lead to some interesting insights.&lt;br /&gt;&lt;br /&gt;If I'm standing at this vantage point, heated discussions like the recent meme-storm in the blogosphere of "&lt;a href="http://www.sauria.com/blog/2008/05/22/the-scala-vs-erlang-whirlwind/"&gt;Erlang&lt;/a&gt;&lt;a href="http://www.sauria.com/blog/2008/05/22/the-scala-vs-erlang-whirlwind/"&gt; vs &lt;/a&gt;&lt;a href="http://www.sauria.com/blog/2008/05/22/the-scala-vs-erlang-whirlwind/"&gt;Scala&lt;/a&gt;" become much less relevant. Is there a platform that gives you reliability, scalability, distribution, hot code replacement that uses Scala as a language? No? Oh. What a pity...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-2790488996638110334?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/2790488996638110334/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=2790488996638110334' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/2790488996638110334'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/2790488996638110334'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/07/notes-from-erlang-exchange.html' title='Notes from Erlang Exchange'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-8901001959287642946</id><published>2008-06-17T06:07:00.001-07:00</published><updated>2008-06-25T01:47:45.188-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Mochiweb'/><category scheme='http://www.blogger.com/atom/ns#' term='REST'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='TDD'/><title type='text'>Migrating a native Erlang interface to RESTful Mochiweb (with a bit of TDD)</title><content type='html'>The title is a bit of a mouthful, but it does contain in essence what I will show you:&lt;br /&gt;&lt;br /&gt;1. I will convert an existing native erlang &lt;a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete"&gt;CRUD&lt;/a&gt; interface (a simple client-server) to use a HTTP layer.&lt;br /&gt;2. The HTTP calls will be &lt;a href="http://en.wikipedia.org/wiki/Representational_State_Transfer"&gt;REST&lt;/a&gt;ful.&lt;br /&gt;3. I will be using &lt;a href="http://code.google.com/p/mochiweb/"&gt;Mochiweb&lt;/a&gt;.&lt;br /&gt;4. I will show you how to do a bit of Test-Driven-Development (TDD) for this exercise  using &lt;a href="https://support.process-one.net/doc/display/CONTRIBS/EUnit"&gt;EUnit&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;As an introduction, I suggest you read &lt;a href="http://steve.vinoski.net/blog/"&gt;Steve Vinoski&lt;/a&gt;'s "&lt;a href="http://www.infoq.com/articles/vinoski-erlang-rest"&gt;RESTful Services with Erlang and Yaws&lt;/a&gt;", and "&lt;a href="http://blog.socklabs.com/2008/04/a_restful_web_service_demo_in/"&gt;A RESTful web service demo in yaws&lt;/a&gt;" by &lt;a href="http://blog.socklabs.com/"&gt;Nick Gerakines.&lt;/a&gt; They are both excellent posts, and Nick's post has a lot of interesting details, including some OTP goodness. Also check out &lt;a href="http://darynholmes.wordpress.com/2008/03/15/beginners-tutorial-routing-in-rails-20-with-rest-part-1-of-n/"&gt;Daryn&lt;/a&gt;'s posts for a Rails twist on this topic.&lt;br /&gt;&lt;br /&gt;I will NOT be showing much error handling, simply returning a 501 response if the REST Url has&lt;br /&gt;some mistake in it.&lt;br /&gt;&lt;br /&gt;This is the plan:&lt;br /&gt;1. We will start with a working set of tests for a native CRUD interface. I will &lt;span style="font-style: italic;"&gt;not&lt;/span&gt; show you the actual implementation, but it is simple enough to do yourself using a process dictionary.&lt;br /&gt;2. I will show a very simple REST interface with Mochiweb, that just returns the request method type as a plaintext string. This will be tested using Inets, the native Erlang OTP internet module.&lt;br /&gt;3. We will write a translators to and from plaintext and native erlang terms (again, unit tested).&lt;br /&gt;4. We will combine all these into the REST interface, tested using Inets again.&lt;br /&gt;&lt;br /&gt;Right, here is the unit tests for the CRUD interface:&lt;pre class="code"&gt;crud_test_() -&gt;   &lt;br /&gt;  {setup,&lt;br /&gt;   fun() -&gt; start() end,&lt;br /&gt;   fun(_) -&gt; stop() end,&lt;br /&gt;   fun(_) -&gt;&lt;br /&gt;           [&lt;br /&gt;            ?_assert(ok == create(#person{id = 1})),&lt;br /&gt;            ?_assert(already_exists == create(#person{id=1})),&lt;br /&gt;            ?_assert(#person{id=1} == retrieve(1)),&lt;br /&gt;            ?_assert(ok == update(#person{id = 1, name="Ben"})),&lt;br /&gt;            ?_assert(#person{id=1, name="Ben"} == retrieve(1)),&lt;br /&gt;            ?_assert(ok == delete(1)),&lt;br /&gt;            ?_assert(undefined == delete(2)),&lt;br /&gt;            ?_assert(undefined == update(#person{id = 2}))&lt;br /&gt;           ]&lt;br /&gt;   end}.&lt;/pre&gt;The CRUD interface is container in a module "crud", and start() and stop() are methods to start and stop the CRUD server. For EUnit, you can define a test with a setup, teardown and tests, and this is the form that I've used here. There are many forms of tests for EUnit, and I suggest you consult the EUnit documentation (contained in the EUnit distribution).&lt;br /&gt;&lt;br /&gt;EUnit automatically creates a test() function on the module when you include "eunit.hrl", so let's run the crud tests:&lt;pre class="console"&gt;&lt;br /&gt;75&gt; crud:test().&lt;br /&gt;All 8 tests successful.&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;We know that are CRUD interface is working OK.&lt;br /&gt;&lt;br /&gt;Onto Step 2! If you're not familiar with Mochiweb, it's a lightweight web server developed by the guys at &lt;a href="http://www.mochimedia.com/"&gt;Mochi Media&lt;/a&gt;. It's very easy to integrate into you application, and has very little configuration.&lt;br /&gt;&lt;br /&gt;Here are the tests for the simple Mochiweb demo:&lt;pre class="code"&gt;&lt;br /&gt;-define(URL, "http://127.0.0.1:8888").&lt;br /&gt;&lt;br /&gt;rest_server_test_() -&gt;&lt;br /&gt;    {setup,&lt;br /&gt;     fun() -&gt; inets:start(), start_simple() end,&lt;br /&gt;     fun(_) -&gt; inets:stop(), stop_simple() end,&lt;br /&gt;     fun(_) -&gt;&lt;br /&gt;             [&lt;br /&gt;              ?_assert(http_result('GET') =:= "GET"),&lt;br /&gt;              ?_assert(http_result('PUT') =:= "PUT"),&lt;br /&gt;              ?_assert(http_result('POST') =:= "POST"),&lt;br /&gt;              ?_assert(http_result('DELETE') =:= "DELETE")&lt;br /&gt;             ]&lt;br /&gt;     end}.&lt;br /&gt;&lt;br /&gt;parse_result(Result) -&gt;&lt;br /&gt;    {ok, {{_Version, 200, _ReasonPhrase}, _Headers, ResultBody}} = Result,&lt;br /&gt;    ResultBody.&lt;br /&gt;&lt;br /&gt;rest_server_test_() -&gt;&lt;br /&gt;    {setup,&lt;br /&gt;     fun() -&gt; inets:start(), start_simple() end,&lt;br /&gt;     fun(_) -&gt; inets:stop(), stop_simple() end,&lt;br /&gt;     fun(_) -&gt;&lt;br /&gt;             [&lt;br /&gt;              ?_assert(http_result('GET') =:= "GET"),&lt;br /&gt;              ?_assert(http_result('PUT') =:= "PUT"),&lt;br /&gt;              ?_assert(http_result('POST') =:= "POST"),&lt;br /&gt;              ?_assert(http_result('DELETE') =:= "DELETE")&lt;br /&gt;             ]&lt;br /&gt;     end}.&lt;br /&gt;&lt;/pre&gt;start_simple() starts the simple version of the server and stop_simple() stops it. We're just using the base URL, and the server just returns a plain-text string of the request method. The http:request() functions are part of Inets (documentation &lt;a href="http://www.erlang.org/doc/apps/inets/index.html"&gt;here&lt;/a&gt;). You will notice that the put and post functions have extra parameters (which we will use later for the request body).&lt;br /&gt;&lt;br /&gt;Here's the solution:&lt;pre class="code"&gt;&lt;br /&gt;start_simple() -&gt;&lt;br /&gt;    mochiweb_http:start(&lt;br /&gt;      [{ip, "127.0.0.1"},&lt;br /&gt;       {loop, {?MODULE, simple_response}}]).&lt;br /&gt;stop_simple() -&gt;&lt;br /&gt;    mochiweb_http:stop().&lt;br /&gt;    &lt;br /&gt;&lt;br /&gt;simple_response(Req, Method) -&gt;&lt;br /&gt;    Req:ok({"text/plain", atom_to_list(Method)}).&lt;br /&gt;simple_response(Req) -&gt;&lt;br /&gt;    simple_response(Req, Req:get(method)).&lt;br /&gt;&lt;/pre&gt;We've started Mochiweb, and told it that the "simple_response" function in the current module should be used to handle requests. It takes one parameter, which contains the request data. The data can be queried with different methods to extract data from it, e.g. Req:get(method), gives you the method used.&lt;br /&gt;&lt;br /&gt;The Req:ok() function is used to respond to a request, and we simply return plain text (with the text/plain MIME type).&lt;br /&gt;&lt;br /&gt;And let's run the tests:&lt;pre class="console"&gt;&lt;br /&gt;77&gt; rest_server:test().&lt;br /&gt;...&lt;br /&gt;All 4 tests successful.&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;Nice. The next bit of code we need is to translate Erlang terms to and from plain text. We'll use Base64 encoding for this. Erlang also has very hand term to binary and binary to term functions that we can use to achieve the translation.&lt;br /&gt;&lt;br /&gt;The tests for the translation:&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;term_to_plaintext_test_() -&gt;&lt;br /&gt;    A = anatom,&lt;br /&gt;    B = {a, 23, "abc"},&lt;br /&gt;    C = {props, [{a,3},{b,4}]},&lt;br /&gt;    [&lt;br /&gt;     ?_assert(A == ptt(ttp(A))),&lt;br /&gt;     ?_assert(B == ptt(ttp(B))),&lt;br /&gt;     ?_assert(C == ptt(ttp(C)))&lt;br /&gt;    ].&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Where "ttp" would be "term_to_plaintext" and "ptt" is&lt;br /&gt;"plaintext_to_term". Here's the implementation:&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;ttp(Term) -&gt;&lt;br /&gt;    base64:encode_to_string(term_to_binary(Term)).&lt;br /&gt;&lt;br /&gt;ptt(PlainText) -&gt;&lt;br /&gt;    binary_to_term(base64:decode(PlainText)).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And the test result:&lt;pre class="console"&gt;&lt;br /&gt;77&gt; rest_server:test().&lt;br /&gt;...&lt;br /&gt;All 7 tests successful.&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;(7 tests, since we have the first 4 and now the extra 3). Now things are getting a bit more complicated. We now have to ensure that the response to the request is converted from the native Erlang terms to plaintext, this result is then returned, and we also need a function to convert the request result into the native form.&lt;br /&gt;&lt;br /&gt;Here we go:&lt;pre class="code"&gt;&lt;br /&gt;result_to_terms(Result) -&gt;&lt;br /&gt;  {ok, {{_Version, 200, _ReasonPhrase}, _Headers, ResultBody}} = Result,&lt;br /&gt;  ptt(ResultBody).&lt;br /&gt;&lt;br /&gt;rest_api('GET', Path) -&gt;&lt;br /&gt;    Result = http:request(get, {?URL ++ Path, []}, [], []),&lt;br /&gt;    result_to_terms(Result);&lt;br /&gt;rest_api('DELETE', Path) -&gt;&lt;br /&gt;    Result = http:request(delete, {?URL ++ Path, []}, [], []),&lt;br /&gt;    result_to_terms(Result).&lt;br /&gt;&lt;br /&gt;rest_api('POST', Path, Data) -&gt;&lt;br /&gt;    Result = http:request(post, {?URL ++ Path, [], [], ttp(Data)}, [], []),&lt;br /&gt;    result_to_terms(Result);&lt;br /&gt;rest_api('PUT', Path, Data) -&gt;&lt;br /&gt;    Result = http:request(put, {?URL ++ Path, [], [], ttp(Data)}, [], []),&lt;br /&gt;    result_to_terms(Result).&lt;br /&gt;&lt;br /&gt;crud_server_test_() -&gt;&lt;br /&gt;    {setup,&lt;br /&gt;     fun() -&gt; crud:start(), inets:start(), start() end,&lt;br /&gt;     fun(_) -&gt; stop(), inets:stop(), crud:stop() end,&lt;br /&gt;     fun(_) -&gt;&lt;br /&gt;      [&lt;br /&gt;       ?_assert(ok == rest_api('POST', "/person", #person{id = 1})),&lt;br /&gt;       ?_assert(already_exists == rest_api('POST', "/person", #person{id = 1})),&lt;br /&gt;       ?_assert(#person{id=1} == rest_api('GET', "/person/1")),&lt;br /&gt;       ?_assert(ok == rest_api('PUT', "/person/1", #person{name="Ben"})),&lt;br /&gt;       ?_assert(#person{id=1, name="Ben"} == rest_api('GET', "/person/1")),&lt;br /&gt;       ?_assert(ok == rest_api('DELETE', "/person/1")),&lt;br /&gt;       ?_assert(undefined == rest_api('DELETE', "/person/1")),&lt;br /&gt;       ?_assert(undefined == rest_api('PUT', "/person/1", #person{id = 2}))&lt;br /&gt;      ]&lt;br /&gt;     end}.&lt;br /&gt;&lt;/pre&gt;There is a bit of duplication here, but for illustration I've kept the functions seperate. You will notice that the data is converted to plaintext for the PUT and POST requests. result_to_terms() converts the HTTP result from the plain text to the native Erlang terms.&lt;br /&gt;&lt;br /&gt;You can scroll back to the original CRUD tests, and notice that the tests do exactly the same thing as on the CRUD interface, but we would have to do a "GET" + "/person/1" to get person 1, instead of doing a crud:retrieve(1).&lt;br /&gt;&lt;br /&gt;The mapping between the CRUD and REST method are as follows:&lt;br /&gt;GET &lt;=&gt; RETRIEVE&lt;br /&gt;POST &lt;=&gt; CREATE&lt;br /&gt;PUT &lt;=&gt; UPDATE&lt;br /&gt;DELETE &lt;=&gt;DELETE&lt;br /&gt;&lt;br /&gt;Here's the final product:&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;start() -&gt;&lt;br /&gt;    mochiweb_http:start(&lt;br /&gt;      [{ip, "127.0.0.1"},&lt;br /&gt;       {loop, {?MODULE, crud_response}}]).&lt;br /&gt;stop() -&gt;&lt;br /&gt;    mochiweb_http:stop().&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;crud_response(Req, 'GET', "/person/" ++ IdString) -&gt;&lt;br /&gt;    Id = list_to_integer(IdString),&lt;br /&gt;    Response = crud:retrieve(Id),&lt;br /&gt;    Req:ok({"text/plain", ttp(Response)});&lt;br /&gt;&lt;br /&gt;crud_response(Req, 'DELETE', "/person/" ++ IdString) -&gt;&lt;br /&gt;    Id = list_to_integer(IdString),&lt;br /&gt;    Response = crud:delete(Id),&lt;br /&gt;    Req:ok({"text/plain", ttp(Response)});&lt;br /&gt;&lt;br /&gt;crud_response(Req, 'POST', "/person") -&gt;&lt;br /&gt;    Body = Req:recv_body(),&lt;br /&gt;    Person = ptt(Body),&lt;br /&gt;    Response = crud:create(Person),&lt;br /&gt;    Req:ok({"text/plain", ttp(Response)});&lt;br /&gt;&lt;br /&gt;crud_response(Req, 'PUT', "/person/" ++ IdString) -&gt;&lt;br /&gt;    Id = list_to_integer(IdString),&lt;br /&gt;    Body = Req:recv_body(),&lt;br /&gt;    PersonWithNewValues = ptt(Body),&lt;br /&gt;    UpdatedPerson = #person{id = Id, &lt;br /&gt;                            name = PersonWithNewValues#person.name, &lt;br /&gt;                            email_address = PersonWithNewValues#person.email_address},&lt;br /&gt;    Response = crud:update(UpdatedPerson),&lt;br /&gt;    Req:ok({"text/plain", ttp(Response)});&lt;br /&gt;        &lt;br /&gt;crud_response(Req, _Method, Path) -&gt;&lt;br /&gt;    Req:respond({501, [], Path}).&lt;br /&gt;&lt;br /&gt;crud_response(Req) -&gt;&lt;br /&gt;    crud_response(Req, Req:get(method), Req:get(path)).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Once again, there is some duplication, but it's easier to grasp when we split the functions. The GET and DELETE requests are simple to handle, since we just convert the Id string to an integer (which could throw an exception, you would have to handle that in some way), and call the CRUD interface.&lt;br /&gt;&lt;br /&gt;POST is not much more complicated, we just get the body of the request using Req:recv_body().&lt;br /&gt;&lt;br /&gt;The PUT function has a problem. There is duplication of the Id of the person, in both the URL "/person/1", and the actual term, #person{id=1,...}. I'm not sure how to handle this, comments are welcome. Perhaps you can generate an error response if the Ids don't match. Or you can make sure the posted record does NOT contain an Id field, and return an error if it does.&lt;br /&gt;&lt;br /&gt;The solution as I've given it, uses the fields in the record, and ignores the Id of the record, using the Id in the URL instead.&lt;br /&gt;&lt;br /&gt;The last function generates an error if the request could not be matched.&lt;br /&gt;&lt;br /&gt;And the moment of truth:&lt;pre class="console"&gt;81&gt; rest_server:test().&lt;br /&gt;...&lt;br /&gt;All 15 tests successful.&lt;br /&gt;ok&lt;/pre&gt;Looks so simple now, but I can asure you that it took some effort to get all the tests to pass!&lt;br /&gt;&lt;br /&gt;Well I hope I've shown you something that you didn't know before, or even encouraged you to learn some Erlang. &lt;br /&gt;&lt;br /&gt;Comments and criticisms are most welcome :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-8901001959287642946?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/8901001959287642946/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=8901001959287642946' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/8901001959287642946'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/8901001959287642946'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/06/migrating-native-erlang-interface-to.html' title='Migrating a native Erlang interface to RESTful Mochiweb (with a bit of TDD)'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-2111907712393486859</id><published>2008-06-08T14:19:00.000-07:00</published><updated>2008-06-25T01:48:11.531-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='REST'/><category scheme='http://www.blogger.com/atom/ns#' term='Charts'/><title type='text'>Restful Charts</title><content type='html'>What would you normally expect of a graph-generation library? I'm talking about the pie charts, bar charts etc. You would normally expect an output to some file format, it being PNG or SVG if you're lucky. BUT, &lt;a href="http://www.infoq.com/articles/bass-google-charts-gchartrb;jsessionid=C2188665B07E95A7BDFC4CD39A1C78D3"&gt;gchartrb&lt;/a&gt; doesn't do this, it creates a URL that you would use to GET the graph from the Google charts API.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;So what? &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Well, it's interesting because it means that &lt;span style="font-style: italic;"&gt;some&lt;/span&gt; people at least consider that generating the graph using a REST API is as reliable as disk, and as convenient as a local library. I've used Google charts before, but I've never thought about integrating it into another program as a service, and I think it's pretty neat. And &lt;span style="font-style: italic;"&gt;easy&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;I'm placing my chips on REST.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-2111907712393486859?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/2111907712393486859/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=2111907712393486859' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/2111907712393486859'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/2111907712393486859'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/06/restful-charts.html' title='Restful Charts'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-6668255548224137560</id><published>2008-05-07T05:14:00.000-07:00</published><updated>2009-02-25T01:54:02.621-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Concurrency'/><category scheme='http://www.blogger.com/atom/ns#' term='Quicksort'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Parallel Quicksort in Erlang - Part II</title><content type='html'>In my &lt;a href="http://21ccw.blogspot.com/2008/04/parallel-quicksort-in-erlang.html"&gt;previous post&lt;/a&gt;, &lt;a href="http://jaksa.wordpress.com/"&gt;Jaksa&lt;/a&gt; pointed out that there's a problem, in that although there may be multiple processes, they will be blocked and waiting (sequencially) for the first peval to complete, then the second peval, etc. Which will result in a pseudo-parallel depth-first quicksort, instead of a breadth-first quicksort. This is the offending line:&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;peval(fun pqsort/1, Left) ++ [Pivot] ++ peval(fun pqsort/1, Right).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I was hoping Erlang would be clever enough to execute the two functions in parallel, but that's a fairly unreasonable (and completely incorrect) assumption! &lt;br /&gt;&lt;br /&gt;But how to fix it? There is a parallel implementation of the map function in &lt;a href="http://www.amazon.com/dp/193435600X?tag=21scencodwor-20&amp;camp=14573&amp;creative=327641&amp;linkCode=as1&amp;creativeASIN=193435600X&amp;adid=0S65GZ2QK77VXZX40RD5&amp;"&gt;Joe's book&lt;/a&gt;, and &lt;a href="http://montsamu.blogspot.com/2007/02/erlang-parallel-map-and-parallel.html"&gt;Montsamu&lt;/a&gt; has a variation of it on his blog:&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;-module(plists).&lt;br /&gt;-export([pmap/2]).&lt;br /&gt;&lt;br /&gt;pmap(F, L) -&gt;&lt;br /&gt;    S = self(),&lt;br /&gt;    Pids = lists:map(fun(I) -&gt; spawn(fun() -&gt; pmap_f(S, F, I) end) end, L),&lt;br /&gt;    pmap_gather(Pids).&lt;br /&gt;&lt;br /&gt;pmap_gather([H|T]) -&gt;&lt;br /&gt;    receive&lt;br /&gt;        {H, Ret} -&gt; [Ret|pmap_gather(T)]&lt;br /&gt;    end;&lt;br /&gt;pmap_gather([]) -&gt;&lt;br /&gt;    [].&lt;br /&gt;&lt;br /&gt;pmap_f(Parent, F, I) -&gt;&lt;br /&gt;    Parent ! {self(), (catch F(I))}.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The pmap_gather function is the interesting one. It will traverse the list of Pids, and wait for each to send back its result. Using pmap, we update pqsort as follows:&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;pqsort([]) -&gt; [];&lt;br /&gt;pqsort([Pivot]) -&gt; [Pivot];&lt;br /&gt;pqsort([Pivot|Rest]) -&gt;&lt;br /&gt;    io:format("+", []),&lt;br /&gt;    Left = [X || X &lt;- Rest, X &lt; Pivot],&lt;br /&gt;    Right = [Y || Y &lt;- Rest, Y &gt;= Pivot],&lt;br /&gt;    [SortedLeft, SortedRight] = plists:pmap(fun pqsort/1, [Left, Right]),&lt;br /&gt;    io:format("-", []),&lt;br /&gt;    SortedLeft ++ [Pivot] ++ SortedRight.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And with a perfectly unbalanced (for quicksort) list to sort, we expect 1 process, then 3 as the first spawns 2 new ones, then down again to zero:&lt;br /&gt;&lt;br /&gt;&lt;pre class="console"&gt;&lt;br /&gt;(emacs@21ccw.blogspot.com)47&gt; pqsort:pqsort([4,2,1,3,6,5,7]).&lt;br /&gt;+++---[1,2,3,4,5,6,7]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;For the same example we had last time, sorting 100 random numbers, the number of concurrent process graph during execution looks like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_bXDgUoT7V7o/SCGdnRJGWeI/AAAAAAAAAHM/3_fhZemNZPs/s1600-h/pqsort.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://3.bp.blogspot.com/_bXDgUoT7V7o/SCGdnRJGWeI/AAAAAAAAAHM/3_fhZemNZPs/s400/pqsort.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5197608742904289762" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Much better!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-6668255548224137560?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/6668255548224137560/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=6668255548224137560' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/6668255548224137560'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/6668255548224137560'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/05/parallel-quicksort-in-erlang-part-ii.html' title='Parallel Quicksort in Erlang - Part II'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_bXDgUoT7V7o/SCGdnRJGWeI/AAAAAAAAAHM/3_fhZemNZPs/s72-c/pqsort.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-6885360353804589417</id><published>2008-04-24T05:36:00.000-07:00</published><updated>2009-02-25T01:52:18.340-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Concurrency'/><category scheme='http://www.blogger.com/atom/ns#' term='Quicksort'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Parallel Quicksort in Erlang</title><content type='html'>&lt;a href="http://jaksa.wordpress.com/2008/04/22/graph-algorithms-with-forkjoin/"&gt;Jaksa&lt;/a&gt; is doing some posts on parallel graph algorithm in Java using fork/join, and he mentions &lt;a href="http://en.wikipedia.org/wiki/Quicksort"&gt;quicksort&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;    "The equivalent of the hello world for parallel languages is the quicksort algorithm"&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Now I'm tempted to say something like "Oh yea? You know how many lines of code quicksort is in Erlang? 3!" Yes &lt;a href="http://en.wikipedia.org/wiki/Erlang_(programming_language)"&gt;really&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;qsort([]) -&gt; [];&lt;br /&gt;qsort([Pivot|Rest]) -&gt;&lt;br /&gt;    qsort([ X || X &lt;- Rest, X &lt; Pivot]) ++ [Pivot] ++ qsort([ Y || Y &lt;- Rest, Y &gt;= Pivot]).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;But this would be wrong, as the terseness of the quicksort implementation says more about the power of &lt;a href="http://egarson.blogspot.com/2008/03/erlang-list-comprehensions-by-necessity.html"&gt;list comprehensions&lt;/a&gt; than about Erlang's concurrency-supporting language features.&lt;br /&gt;&lt;br /&gt;So then let's see how you could implement a concurrent quicksort in Erlang, which &lt;i&gt;will&lt;/i&gt; say something about the way in which we can implement concurrency in Erlang. We'll start with the original Wikipedia quicksort from above, and alter it a little bit to make it more obvious what I'm going to do next:&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;qsort([]) -&gt; [];&lt;br /&gt;qsort([Pivot|Rest]) -&gt;&lt;br /&gt;    Left = [X || X &lt;- Rest, X &lt; Pivot],&lt;br /&gt;    Right = [Y || Y &lt;- Rest, Y &gt;= Pivot],&lt;br /&gt;    qsort(Left) ++ [Pivot] ++ qsort(Right).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Note the two calls to qsort at the end. Since there is no shared state between these two functions, they can be executed in parallel, and the results can be concatenated to yield the sorted result. Depending on the distribution of the input, each of these calls could be split up into 2 more qsort calls etc. etc. Note that you could do a hybrid recursive-tail-recursive solution which sorts the smallest partition using normal recursion and the largest using tail-recusrion, but that is not the aim here.&lt;br /&gt;&lt;br /&gt;Let's assume that we already have a function that executes any input function in a separate Erlang process called "peval". (Note Erlang processes are extremely light-weight and take very little time to construct. They're are &lt;i&gt;not&lt;/i&gt; modelled as operating system processes). peval takes two arguments, a function to execute, and the arguments to the function. When called, it looks like a normal function call:&lt;br /&gt;&lt;br /&gt;&lt;pre class="console"&gt;&lt;br /&gt;(emacs@21ccw.blogspot.com)38&gt; pqsort:peval(fun(X) -&gt; X*X end, 13).&lt;br /&gt;169&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;But, in the background, it spawns a process and the function is evaluated on this new process. The result is then returned to the calling process. Using peval, we now have a parallel quicksort:&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;pqsort([]) -&gt; [];&lt;br /&gt;pqsort([Pivot|Rest]) -&gt;&lt;br /&gt;    Left = [X || X &lt;- Rest, X &lt; Pivot],&lt;br /&gt;    Right = [Y || Y &lt;- Rest, Y &gt;= Pivot],&lt;br /&gt;    peval(fun pqsort/1, Left) ++ [Pivot] ++ peval(fun pqsort/1, Right).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now there is a significant observation to make. The concurrent and non-concurrent versions look remarkably similar, we only had to substitute the original function evaluations with parallel versions. This is one of the reasons why I think functional languages are better suited to concurrency that OO ones.&lt;br /&gt;&lt;br /&gt;Let's look at peval. peval creates a new process using spawn_link. spawn_link() is used instead of spawn(), so that the creator process receives exception when the new child process throws an exception.&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;peval(Fun, Args) -&gt;&lt;br /&gt;    Pid = spawn_link(fun() -&gt; wait() end),&lt;br /&gt;    Pid ! {self(), Fun, Args},&lt;br /&gt;    receive &lt;br /&gt;        {Pid, R} -&gt; R&lt;br /&gt;    end.&lt;br /&gt;&lt;br /&gt;wait() -&gt;&lt;br /&gt;    receive&lt;br /&gt;        {From,Fun,Args} -&gt; &lt;br /&gt;            From ! {self(), Fun(Args)}&lt;br /&gt;    end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That's it! Parallel quicksort in about &lt;i&gt;15&lt;/i&gt; lines of Erlang code. If I add a "+" output to console on every entry to peval, and "-" on every exit from peval, you get the following output:&lt;br /&gt;&lt;br /&gt;&lt;pre class="console"&gt;&lt;br /&gt;(emacs@21ccw.blogspot.com)18&gt; L1 = lists:map(fun(X) -&gt; trunc(random:uniform()*100) end, lists:seq(1,100)).&lt;br /&gt;[57,33,2,85,59,65,61,71,83,75,94,94,19,5,79,53,31,54,39,61,&lt;br /&gt; 61,79,55,6,37,35,86,31,88|...]&lt;br /&gt;&lt;br /&gt;(emacs@21ccw.blogspot.com&gt; pqsort:pqsort(L1).&lt;br /&gt;+++-++++-+--++-+++++-+--+--+--+++-+--++-+------+++&lt;br /&gt;+-++-+---+++-+--+---++-+-----+++++-+--++-+---+++++&lt;br /&gt;-+--+--+++-+--+---++-+----++-++-++-+------++++-+--&lt;br /&gt;++++-+--++-+---++++-+--++-+---++++-+--+++-++-+---+&lt;br /&gt;++-+--+----+-----+++-+++-+--+++-++-+---+----++-+--&lt;br /&gt;--&lt;br /&gt;[2,4,5,6,7,9,11,13,14,17,18,19,20,21,22,24,27,31,32,33,35,&lt;br /&gt; 37,38,39,40,42,44,47,48|...]&lt;br /&gt;&lt;br /&gt;(emacs@21ccw.blogspot.com)5&gt; pqsort:integrate_output("+++-++++-+--++-+++++-+--+--+--+++-+--++-+------+++").&lt;br /&gt;0123234565654565678910910989878767898987898987654345...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You can see that for the first few pevals, by integrating over the output, that number of parallel processes goes up to around 8-10. This will depend on the distribution, and the relative times it takes to initialise processes to doing the actual computation etc. &lt;br /&gt;&lt;br /&gt;Now that we've got pqsort going, I'm looking forward to doing the parallel graph algorithms of Jaksa's fork/join graph algorithms. There will be an obstacle to ensure that no nodes are visited more than once, but this could (possibly) be solved by partitioning the graph into subgraphs first where no children have multiple parents. We'll see...&lt;br /&gt;&lt;br /&gt;&lt;span class="updates"&gt;Update (7 May 2008): This implementation is wrong! (see comments). There's a follow-up post here: &lt;/span&gt;&lt;a href="http://21ccw.blogspot.com/2008/05/parallel-quicksort-in-erlang-part-ii.html"&gt;Parallel Quicksort in Erlang - Part II&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-6885360353804589417?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/6885360353804589417/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=6885360353804589417' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/6885360353804589417'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/6885360353804589417'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/04/parallel-quicksort-in-erlang.html' title='Parallel Quicksort in Erlang'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-3297377195562942494</id><published>2008-04-18T10:13:00.000-07:00</published><updated>2008-04-21T06:06:42.810-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='XPlanner'/><category scheme='http://www.blogger.com/atom/ns#' term='Internet Explorer'/><category scheme='http://www.blogger.com/atom/ns#' term='IE'/><title type='text'>IE7 Page Zoom Broken</title><content type='html'>We use &lt;a href="http://www.xplanner.org/"&gt;XPlanner&lt;/a&gt; at work for some projects, and we noticed that there seems to be a problem with the hyperlinks in Internet Explorer (version 7.0.5730.11) when the pages are zoomed in (you can zoom pages in IE with Ctrl +). The horizontal areas for the links were not aligned to the actual text, which means that some links seem &lt;span style="font-style: italic;"&gt;can't be clicked correctly&lt;/span&gt;, but you &lt;span style="font-style: italic;"&gt;can&lt;/span&gt; click white space (look carefully at the first image, you can see the normal I-beam cursor):&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_bXDgUoT7V7o/SAjcyclT7uI/AAAAAAAAAG8/uXDb8m13Ddo/s1600-h/one.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_bXDgUoT7V7o/SAjcyclT7uI/AAAAAAAAAG8/uXDb8m13Ddo/s400/one.png" alt="" id="BLOGGER_PHOTO_ID_5190641329768754914" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_bXDgUoT7V7o/SAjcyslT7vI/AAAAAAAAAHE/r-iBAhrJyJU/s1600-h/two.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_bXDgUoT7V7o/SAjcyslT7vI/AAAAAAAAAHE/r-iBAhrJyJU/s400/two.png" alt="" id="BLOGGER_PHOTO_ID_5190641334063722226" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;I shouldn't be surprised, since it does &lt;a href="http://21ccw.blogspot.com/2008/03/acid3-test-released.html"&gt;so badly in the Acid3 test&lt;/a&gt;. I couldn't find any other reports of this issue, and will be happy to link to them if you post a comment.&lt;br /&gt;&lt;br /&gt;P.S. Firefox, Opera and Safari don't have this problem.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-3297377195562942494?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/3297377195562942494/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=3297377195562942494' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/3297377195562942494'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/3297377195562942494'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/04/ie7-page-zoom-broken.html' title='IE7 Page Zoom Broken'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_bXDgUoT7V7o/SAjcyclT7uI/AAAAAAAAAG8/uXDb8m13Ddo/s72-c/one.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-7636161167002905275</id><published>2008-04-10T06:35:00.000-07:00</published><updated>2008-04-11T01:42:48.223-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Mnesia'/><category scheme='http://www.blogger.com/atom/ns#' term='Distributed databases'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Getting started with distributed Erlang - Mnesia table relocation</title><content type='html'>&lt;a href="http://www.erlang.org/doc/apps/mnesia/index.html"&gt;Mnesia&lt;/a&gt; is a distributed database that forms part of the &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt; release. One of the features that I think is potentially powerful, is &lt;span style="font-style: italic;"&gt;transparent&lt;/span&gt; table relocation across machines. With Mnesia, you can replicate tables to any nodes you wish in your network, and Mnesiatakes care of all the back end bits for you. With "transparent", I mean that you don't need to do anything in your clients to make them "aware" of the new tables. Reads that were taking place from a table on one machine, will now be distributed across multiple nodes (where the nodes reside on single or multiple machines).&lt;br /&gt;&lt;br /&gt;I wanted to see how difficult it is to achieve this. For the setup, I installed two virtual Ubuntu 7.10 machines using &lt;a href="http://www.vmware.com/products/player/"&gt;VMware Player&lt;/a&gt;. You can get images for most Ubuntu distros at &lt;a href="http://isv-image.ubuntu.com/vmware/"&gt;http://isv-image.ubuntu.com/vmware/&lt;/a&gt;. FYI, the username and password for these images is ubuntu:ubuntu. I named the two nodes&lt;br /&gt;&lt;br /&gt;node1.21ccw.blogspot.com and&lt;br /&gt;node2.21ccw.blogspot.com&lt;br /&gt;&lt;br /&gt;You'll need to edit the network configurations with the IP addresses if you want to reproduce this experiment. If you need some help, post a question as comment :)&lt;br /&gt;&lt;br /&gt;I now had two machines that could ping each other using the full names, and a warm and fuzzy feeling inside:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_bXDgUoT7V7o/R_4bNw2zkfI/AAAAAAAAAG0/by5WJ18M21A/s1600-h/vms.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_bXDgUoT7V7o/R_4bNw2zkfI/AAAAAAAAAG0/by5WJ18M21A/s400/vms.jpg" alt="" id="BLOGGER_PHOTO_ID_5187613744044413426" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The next step was to start up an Erlang node on each machine. There's a catch here though. I got some problems using erl -sname, probably because of the way I set up the hostnames of the machines. So, &lt;span style="font-style: italic;"&gt;I had to specify the fully qualified names manually&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="consoleA"&gt;&lt;br /&gt;ubuntu@node1:~/node1$ erl -name 'node1@node1.21ccw.blogspot.com'&lt;br /&gt;Erlang (BEAM) emulator version 5.5.5 [source] [async-threads:0] [kernel-poll:false]&lt;br /&gt;&lt;br /&gt;Eshell V5.5.5  (abort with ^G)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;pre class="consoleB"&gt;&lt;br /&gt;ubuntu@node2:~/node2$ erl -name 'node2@node2.21ccw.blogspot.com'&lt;br /&gt;Erlang (BEAM) emulator version 5.5.5 [source] [async-threads:0] [kernel-poll:false]&lt;br /&gt;&lt;br /&gt;Eshell V5.5.5  (abort with ^G)&lt;br /&gt;(node1@node1.21ccw.blogspot.com)1&gt; nodes().&lt;br /&gt;[]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Notice the output of the &lt;span style="font-style: italic;"&gt;nodes() &lt;/span&gt;command. This will return a list of &lt;span style="font-style: italic;"&gt;other&lt;/span&gt; Erlang nodes that this node is aware of. Initially there's no awareness. To let a node know of another node, you can use net_adm:ping/1 to ping the other node. Both nodes will then become be aware of each other:&lt;br /&gt;&lt;br /&gt;&lt;pre class="consoleA"&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)4&gt; net_adm:ping('node2@node2.21ccw.blogspot.com').&lt;br /&gt;pong&lt;br /&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)5&gt; nodes().&lt;br /&gt;['node1@node1.21ccw.blogspot.com']&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;pre class="consoleB"&gt;&lt;br /&gt;(node2@node2.21ccw.blogspot.com)1&gt; nodes().&lt;br /&gt;['node1@node1.21ccw.blogspot.com']&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Cool. Now the nodes know of each other. To get Mnesia started, you have to create a schema on each node. A schema is located on the file system, in the same location where the actual disc-copies of tables will reside. [node()|nodes()] creates a list of the current node and all the other connected  nodes. ls() shows the directory that Mnesia has created for the database.&lt;br /&gt;&lt;br /&gt;&lt;pre class="consoleA"&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)5&gt; mnesia:create_schema([node()|nodes()]).&lt;br /&gt;ok&lt;br /&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)6&gt; ls().&lt;br /&gt;Mnesia.node1@node1.21ccw.blogspot.com&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="consoleB"&gt;&lt;br /&gt;(node2@node2.21ccw.blogspot.com)2&gt; ls().&lt;br /&gt;Mnesia.node2@node2.21ccw.blogspot.com&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now we have to start Mnesia on both nodes. You will notice that when we do an mnesia:info on node2 at this point, that it shows both nodes as being running database nodes.&lt;br /&gt;&lt;br /&gt;&lt;pre class="consoleA"&gt;&lt;br /&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)8&gt; mnesia:start().&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="consoleB"&gt;&lt;br /&gt;(node2@node2.21ccw.blogspot.com)3&gt; mnesia:start().&lt;br /&gt;ok&lt;br /&gt;&lt;br /&gt;(node2@node2.21ccw.blogspot.com)4&gt; mnesia:info().&lt;br /&gt;...&lt;br /&gt;running db nodes   = ['node1@node1.21ccw.blogspot.com','node2@node2.21ccw.blogspot.com']&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Next we'll create an actual database table, and populate it with some data. We define a record using rd(), then create a table on node1 (by default, this table will reside in RAM and have a disc copy), write a record to it and then read the record again. The primary key of the table is the first field of the record, i.e. the name.&lt;br /&gt;&lt;br /&gt;&lt;pre class="consoleA"&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)9&gt; rd(person, {name, email_address}).&lt;br /&gt;person&lt;br /&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)10&gt; mnesia:create_table(person, [{attributes, record_info(fields, person)}, {disc_copies, [node()]}]).&lt;br /&gt;{atomic,ok}&lt;br /&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)11&gt; mnesia:transaction(fun() -&gt; mnesia:write(#person{name = "John", email_address = "john@21ccw.blogspot.com"}) end).&lt;br /&gt;{atomic,ok}&lt;br /&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)14&gt; mnesia:transaction(fun() -&gt; mnesia:read({person, "John"}) end).&lt;br /&gt;{atomic,[#person{name = "John",email_address = "john@21ccw.blogspot.com"}]}&lt;br /&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)15&gt; mnesia:info().                                     ...&lt;br /&gt;...&lt;br /&gt;[{'node1@node1.21ccw.blogspot.com',disc_copies}] = [person]&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;What happens when we do the same read on node2? Remember that node has access to the person table &lt;span style="font-style: italic;"&gt;only&lt;/span&gt; via the network, since it resides in RAM and on disc on node1.&lt;br /&gt;&lt;pre class="consoleB"&gt;&lt;br /&gt;node2@node2.21ccw.blogspot.com)5&gt; mnesia:transaction(fun() -&gt; mnesia:read({person, "John"}) end).&lt;br /&gt;{atomic,[{person,"John","john@21ccw.blogspot.com"}]}&lt;br /&gt;&lt;/pre&gt;Nice. Mnesia has transparently read the record from a table that's on another machine :)&lt;br /&gt;&lt;br /&gt;Now we decide to copy the table to node2. This requires a single command. Mnesia does the copying of the actual data for you to the other machine, and when you look at the file system on node2, there will now be "person.DCD" file, which is the disc copy of the table.&lt;br /&gt;&lt;br /&gt;&lt;pre class="consoleA"&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)15&gt; mnesia:add_table_copy(person, 'node2@node2.21ccw.blogspot.com', disc_copies).&lt;br /&gt;{atomic,ok}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="consoleB"&gt;&lt;br /&gt;(node2@node2.21ccw.blogspot.com)9&gt; ls("Mnesia.node2@node2.21ccw.blogspot.com").&lt;br /&gt;DECISION_TAB.LOG     LATEST.LOG           person.DCD&lt;br /&gt;schema.DAT&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;At this point, when you do a query on the person table, the actual data can come from  either node. I'm not sure how Mnesia decides how to distribute the data, that's something to investigate further.&lt;br /&gt;&lt;br /&gt;Since the table is resident on both nodes, we can actually delete it from node1, and doing a read on node1 will now read the table over the network from node2:&lt;br /&gt;&lt;br /&gt;&lt;pre class="consoleA"&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)23&gt; mnesia:del_table_copy(person, node()).&lt;br /&gt;{atomic,ok}&lt;br /&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)19&gt; mnesia:info().&lt;br /&gt;...&lt;br /&gt;[{'node2@node2.21ccw.blogspot.com',disc_copies}] = [person]&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;(node1@node1.21ccw.blogspot.com)18&gt; mnesia:transaction(fun() -&gt; mnesia:read({person, "John"}) end).&lt;br /&gt;{atomic,[#person{name = "John",email_address = "john@21ccw.blogspot.com"}]}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Cool.&lt;br /&gt;&lt;br /&gt;What I've show is how to start up an Erlang/Mnesia node on two machines that are networked together, create tables on either node, and move the tables to other nodes by copying and then deleting them. Mnesia has the ability to configure tables to be RAM only, RAM and disc and disc only, which gives you lots of power for optimisation. Couple this with the fact that you can change your configuration dynamically and you have powerful, dynamically configurable distributed database!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-7636161167002905275?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/7636161167002905275/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=7636161167002905275' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/7636161167002905275'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/7636161167002905275'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/04/getting-started-with-distributed-erlang.html' title='Getting started with distributed Erlang - Mnesia table relocation'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_bXDgUoT7V7o/R_4bNw2zkfI/AAAAAAAAAG0/by5WJ18M21A/s72-c/vms.jpg' height='72' width='72'/><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-7476475743087456600</id><published>2008-04-02T06:37:00.000-07:00</published><updated>2008-04-02T06:40:51.935-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Unit Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='OTP'/><category scheme='http://www.blogger.com/atom/ns#' term='Rakefile'/><category scheme='http://www.blogger.com/atom/ns#' term='Rake'/><category scheme='http://www.blogger.com/atom/ns#' term='Make'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Using Rake for Erlang Unit Testing</title><content type='html'>My previous Erlang/Yaws project, &lt;a href="http://www.dayfindr.com/"&gt;dayfindr.com&lt;/a&gt;, is ticking along nicely:&lt;br /&gt;&lt;pre class="console"&gt;$ yaws --status --id dayfindr&lt;br /&gt;Uptime: 33 Days, 13 Hours, 38 Minutes&lt;br /&gt;&lt;/pre&gt;The feedback has been mostly positive, although I've had a "That's a very irritating site." comment. Well, you can't please everyone!&lt;br /&gt;&lt;br /&gt;Following on the experience gained with dayfindr, I'm starting a new Lyme project which will be using some of the OTP design principles (Think of OTP as Erlang application design patterns). Using OTP will give me hot upgrades, which dayfindr lacks at the moment. The first step that I encountered towards this goal was getting my directory structure to conform to the OTP application structure:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;/project      [The project root]&lt;br /&gt;   /ebin      [The compiled .beam files]&lt;br /&gt;   /src       [The source .erl files]&lt;br /&gt;   /include   [Include files like record definitions]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I've been using the Makefile that's provided in the &lt;a href="http://www.amazon.com/gp/product/193435600X?ie=UTF8&amp;amp;tag=21scencodwor-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=193435600X"&gt;Erlang book&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=21scencodwor-20&amp;amp;l=as2&amp;amp;o=1&amp;amp;a=193435600X" alt="" style="border: medium none  ! important; margin: 0px ! important;" border="0" height="1" width="1" /&gt;, which is quite simple and compiles the beam files to the same directory as the source folder, which doesn't comply to the required directory structure. Now, I could just update the Makefile, but Makefiles have a cryptic syntax that I don't really want to spend time learning. Plus, they're difficult to debug in my experience. So I Googled around a bit for erlang Makefiles with little to disappointing success. Then I saw an interesting link:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://seangeo.blogspot.com/2007/09/building-erlang-with-rake.html"&gt;Building Erlang with Rake&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;When they make a movie of my life and this moment, you will hear an orchestra and a choir of baritones singing "Eureka" when I click on the link.&lt;br /&gt;&lt;br /&gt;Rake is an Ruby equivalent of Make, and more. It took some effort to get it working, since I had rake 0.7.1 on my machine, but trying to find the problem taught me a bit of Ruby in the process. Upgrading to 0.7.3 solved the problem. Sean's Rakefile compiles your src files into the ebin directory very nicely! After tinkering around with Rake, I realised that it's a &lt;span style="font-style: italic;"&gt;really &lt;/span&gt;nice tool:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;It has a nice mix of declarative and imperative code. You can define rules (e.g. always compile .erl to .beam), or tasks (which can be imperative, e.g. running unit tests).&lt;/li&gt;&lt;li&gt;You can use the full power of Ruby, and don't need to learn Make.&lt;/li&gt;&lt;li&gt;It has syntax that is very close to the domain (i.e. it's a good DSL)&lt;/li&gt;&lt;li&gt;It's easy to debug, since you can use the normal Ruby puts functions etc.&lt;/li&gt;&lt;li&gt;How you set up you dependencies is completely up to you, e.g. you can have different rules for files that conform to different regular expressions.&lt;/li&gt;&lt;/ul&gt;After I got this working, inspired with confidence, I decided to integrate my unit testing into the Rakefile. I think it's important to note at this point that I have less than a day's Ruby experience, and it was &lt;span style="font-style: italic;"&gt;easy &lt;/span&gt;to get this working. Hacking a Makefile would probably have taken me hours and hours. The idea is that the test task is dependent on the compile task, so that if you do a "rake test", it will compile anything that's new and run the unit tests. You can just compile with "rake compile".&lt;br /&gt;&lt;br /&gt;In order to get this going I created two Erlang files, foo.erl and bar.erl. Here's bar.erl, which contains two functions, and a test (&lt;a href="http://https//support.process-one.net/doc/display/CONTRIBS/EUnit"&gt;EUnit&lt;/a&gt;)for each function. One of the tests will fail:&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;&lt;br /&gt;-module(bar).&lt;br /&gt;-export([bar1/0, bar2/0]).&lt;br /&gt;&lt;br /&gt;-ifdef(EUNIT).&lt;br /&gt;-include_lib("eunit/include/eunit.hrl").&lt;br /&gt;-endif.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;bar1() -&gt;&lt;br /&gt;  ok.&lt;br /&gt;&lt;br /&gt;bar2() -&gt;&lt;br /&gt;  okk.&lt;br /&gt;&lt;br /&gt;-ifdef(EUNIT).&lt;br /&gt;bar1_test_() -&gt;&lt;br /&gt;  [&lt;br /&gt;    ?_assert(ok == bar1())&lt;br /&gt;  ].&lt;br /&gt;-endif.&lt;br /&gt;&lt;br /&gt;-ifdef(EUNIT).&lt;br /&gt;bar2_test_() -&gt;&lt;br /&gt;  [&lt;br /&gt;    ?_assert(ok == bar2())&lt;br /&gt;  ].&lt;br /&gt;-endif.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;Notice that the unit test code is only compiled into the beam file when the EUNIT flag is set. You can set this in the Rakefile. The unit tests are in the same source file, so we can also test non-exported functions.&lt;br /&gt;&lt;br /&gt;Now let's look at the Rakefile:&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;&lt;br /&gt;require 'rake/clean'&lt;br /&gt;&lt;br /&gt;INCLUDE = "include"&lt;br /&gt;&lt;br /&gt;ERLC_FLAGS = "-I#{INCLUDE} +warn_unused_vars +warn_unused_import"&lt;br /&gt;&lt;br /&gt;SRC = FileList['src/*.erl']&lt;br /&gt;OBJ = SRC.pathmap("%{src,ebin}X.beam")&lt;br /&gt;&lt;br /&gt;CLEAN.include("ebin/*.beam")&lt;br /&gt;&lt;br /&gt;directory 'ebin'&lt;br /&gt;&lt;br /&gt;rule ".beam" =&gt; ["%{ebin,src}X.erl"] do |t|&lt;br /&gt;  sh "erlc -D EUNIT -pa ebin -W #{ERLC_FLAGS} -o ebin #{t.source}"&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;task :compile =&gt; ['ebin'] + OBJ&lt;br /&gt;&lt;br /&gt;task :default =&gt; :compile&lt;br /&gt;&lt;br /&gt;task :run_tests =&gt; [:compile] do&lt;br /&gt;  puts "Modules under test:"&lt;br /&gt;  OBJ.each do |obj|&lt;br /&gt;    obj[%r{.*/(.*).beam}]&lt;br /&gt;    mod = $1&lt;br /&gt;    test_output = `erl -pa ebin -run #{mod} test -run init stop`&lt;br /&gt;&lt;br /&gt;    if /\*failed\*/ =~ test_output&lt;br /&gt;      test_output[/(Failed.*Aborted.*Skipped.*Succeeded.*$)/]&lt;br /&gt;    else&lt;br /&gt;      test_output[/1&gt;\s*(.*)\n/]&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    puts "#{mod}: #{$1}"&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;The juicy bits are the rule and the "run_tests" task. The rule states "for each file ending in .erl, compile the beam file using erlc and put the beam file in ebin". The run_tests task starts up an erlang runtime for each module, and calls test() for that module. The test output is captured and parsed using regular expressions. I know the Ruby code can be improved, so comments are most welcome.&lt;br /&gt;&lt;br /&gt;Here's what happens when I compile and run the tests:&lt;br /&gt;&lt;br /&gt;&lt;pre class="console"&gt;&lt;br /&gt;$ rake clean&lt;br /&gt;(in /home/bjnortier/development/project1)&lt;br /&gt;&lt;br /&gt;$ rake&lt;br /&gt;(in /home/bjnortier/development/project1)&lt;br /&gt;erlc -D EUNIT -pa ebin -W -Iinclude +warn_unused_vars +warn_unused_import -o ebin src/foo.erl&lt;br /&gt;erlc -D EUNIT -pa ebin -W -Iinclude +warn_unused_vars +warn_unused_import -o ebin src/bar.erl&lt;br /&gt;&lt;br /&gt;$rake run_tests&lt;br /&gt;(in /home/bjnortier/development/project1)&lt;br /&gt;Modules under test:&lt;br /&gt;foo: All 2 tests successful.&lt;br /&gt;bar: Failed: 1.  Aborted: 0.  Skipped: 0.  Succeeded: 1.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;At this points I have cleaning, compiling and running the tests for each module. Nice. I'm quite pleased with how it works at the moment, but there's still quite a  bit of work to be done:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Concatenating the running of the unit tests into one no-shell erlang execution. Actually running the tests are very fast, but the shell termination takes about a second or so. Thus, for each module, there is a approximately a second of extra time. Running all the tests in the same erlang session will require some more text parsing, but it's do-able.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Displaying the failed tests. Seeing "Failed: 1" is not very useful for determining what went wrong, so I'll update the parsing to include the failures and errors.&lt;/li&gt;&lt;li&gt;Probably change the "run_tests" task to just "test"&lt;/li&gt;&lt;li&gt;Continuous integration that hooks into version control.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;If there is significant progress I'll post the results. I hope you can use some of it :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-7476475743087456600?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/7476475743087456600/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=7476475743087456600' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/7476475743087456600'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/7476475743087456600'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/04/using-rake-for-erlang-unit-testing.html' title='Using Rake for Erlang Unit Testing'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-308992736105091102</id><published>2008-04-01T04:25:00.000-07:00</published><updated>2008-04-02T03:16:46.309-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Big Mac Index'/><category scheme='http://www.blogger.com/atom/ns#' term='Apple'/><category scheme='http://www.blogger.com/atom/ns#' term='Macbook Pro'/><title type='text'>The MacbookPro Index</title><content type='html'>I'm looking into prices for a Macbook Pro, so that I can &lt;a href="http://21ccw.blogspot.com/2008/03/mac-is-back.html"&gt;bolster my own prediction&lt;/a&gt;. I'm aware of the fact that for some reason, they are a bit more expensive in the UK than in the US (even without sales tax/VAT). I'm from South Africa, so I can organise for someone to bring me one, but I would &lt;span style="font-style: italic;"&gt;never have guessed&lt;/span&gt; that it would be cheaper to buy it there than in the UK, and even &lt;span style="font-style: italic;"&gt;cheaper than the states&lt;/span&gt; if you ignore sales tax/VAT.&lt;br /&gt;&lt;br /&gt;Here's a summary of the prices for the 3 models in Us Dollars (USD), Canadian Dollars (CAD), Punds Sterling (GBP), South African Rand (ZAR) and Hong Kong Dollars (HKD). Prices were taken from the official apple websites, except in South African where the &lt;span style="text-decoration: underline;"&gt;&lt;/span&gt;&lt;a href="http://www.zastore.co.za/"&gt;ZA Store&lt;/a&gt; is one of the official retailers.&lt;br /&gt;&lt;br /&gt;&lt;table class="nobr"&gt;&lt;br /&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;br /&gt;&lt;/td&gt;&lt;th&gt;15" 2.4GHz&lt;/th&gt;&lt;th&gt;15"2.5 GHz&lt;/th&gt;&lt;th&gt;17" 2.5GHz&lt;/th&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;US&lt;/td&gt;&lt;td&gt;1999&lt;/td&gt;&lt;td&gt;2499&lt;/td&gt;&lt;td&gt;2799&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;CAD&lt;/td&gt;&lt;td&gt;2099&lt;/td&gt;&lt;td&gt;2599&lt;/td&gt;&lt;td&gt;2899&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;GBP&lt;/td&gt;&lt;td&gt;1299&lt;/td&gt;&lt;td&gt;1599&lt;/td&gt;&lt;td&gt;1799&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;ZAR&lt;/td&gt;&lt;td&gt;18499&lt;/td&gt;&lt;td&gt;22999&lt;/td&gt;&lt;td&gt;25999&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;HKD&lt;/td&gt;&lt;td&gt;15400&lt;/td&gt;&lt;td&gt;19200&lt;/td&gt;&lt;td&gt;21500&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;Using the following VAT and exchange rates (as on 31/3/2008 and 1/4/2008. A quick investigation revealed Hong Kong at 0%. I could be wrong):&lt;br /&gt;&lt;br /&gt;&lt;table class="nobr"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Country&lt;/th&gt;&lt;th&gt;VAT&lt;br /&gt;&lt;/th&gt;&lt;th&gt;&lt;a href="http://www.xe.com/"&gt;Exchange Rate&lt;/a&gt;&lt;/th&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;USD&lt;/td&gt;&lt;td&gt;0&lt;br /&gt;&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;CAD&lt;/td&gt;&lt;td&gt;0&lt;br /&gt;&lt;/td&gt;&lt;td&gt;1.02&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;GBP&lt;/td&gt;&lt;td&gt;18&lt;br /&gt;&lt;/td&gt;&lt;td&gt;0.50&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;ZAR&lt;/td&gt;&lt;td&gt;14&lt;br /&gt;&lt;/td&gt;&lt;td&gt;8.12&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;HKD&lt;/td&gt;&lt;td&gt;0&lt;br /&gt;&lt;/td&gt;&lt;td&gt;7.79&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;we get the pre-tax cost in US Dollars:&lt;br /&gt;&lt;br /&gt;&lt;table class="nobr"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;br /&gt;&lt;/td&gt;&lt;th&gt;15" 2.4GHz&lt;/th&gt;&lt;th&gt;15"2.5 GHz&lt;/th&gt;&lt;th&gt;17" 2.5GHz&lt;/th&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;US&lt;/td&gt;&lt;td&gt;1999&lt;/td&gt;&lt;td&gt;2499&lt;/td&gt;&lt;td&gt;2799&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;CAD&lt;/td&gt;&lt;td&gt;2049.8&lt;/td&gt;&lt;td&gt;2538.09&lt;/td&gt;&lt;td&gt;2813.05&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;GBP&lt;/td&gt;&lt;td&gt;2197.88&lt;/td&gt;&lt;td&gt;2705.47&lt;/td&gt;&lt;td&gt;3043.86&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;ZAR&lt;/td&gt;&lt;td&gt;1997.93&lt;/td&gt;&lt;td&gt;2483.94&lt;/td&gt;&lt;td&gt;2807.95&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;HKD&lt;/td&gt;&lt;td&gt;1977.64&lt;/td&gt;&lt;td&gt;2465.63&lt;/td&gt;&lt;td&gt;2760.99&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;At a glance, the UK price is consistently higher than any others, and remember this is &lt;span style="font-style: italic;"&gt;without VAT&lt;/span&gt;. UK VAT is at 17.5%, the highest of the 5, which will make the post-tax prices &lt;span style="font-style: italic;"&gt;even higher&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;And if you prefer a pretty picture (&lt;a href="http://code.google.com/apis/chart/"&gt;using Google Charts API&lt;/a&gt;):&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_bXDgUoT7V7o/R_IlQ3pEvwI/AAAAAAAAAGc/Sp-6Rq1HaZQ/s1600-h/MacDiff.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_bXDgUoT7V7o/R_IlQ3pEvwI/AAAAAAAAAGc/Sp-6Rq1HaZQ/s400/MacDiff.png" alt="" id="BLOGGER_PHOTO_ID_5184247092801224450" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;And now onto the MacbookPro index. If you haven't heard about the &lt;a href="http://en.wikipedia.org/wiki/Big_Mac_Index"&gt;Big Mac&lt;/a&gt; index, it is an informal way of measuring purchasing power parity between countries. It can be used to estimate if a currency is over or undervalued, since a Big Mac is pretty much the same in every country. It is calculated by dividing the price of a Big Mac in one country, by that it another country, and comparing this value to the actual exchange rate.&lt;br /&gt;&lt;br /&gt;For the MacbookPro index, I've used an average price (non-weighted for simplicity), and here are the results:&lt;br /&gt;&lt;br /&gt;&lt;table class="nobr"&gt;&lt;br /&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Currency&lt;/th&gt;&lt;th&gt;Under valuation [%]&lt;/th&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;CAD&lt;/td&gt;&lt;td&gt;0.02&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;GBP&lt;/td&gt;&lt;td&gt;8.91&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;ZAR&lt;/td&gt;&lt;td&gt;-0.1&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;HKD&lt;/td&gt;&lt;td&gt;-1.27&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;and the graph:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_bXDgUoT7V7o/R_IwtnpEvyI/AAAAAAAAAGs/frmR3f3kojI/s1600-h/MacbookProIndex.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_bXDgUoT7V7o/R_IwtnpEvyI/AAAAAAAAAGs/frmR3f3kojI/s400/MacbookProIndex.png" alt="" id="BLOGGER_PHOTO_ID_5184259681350369058" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;You could conclude that the pound is undervalued by about 9%. I wouldn't. I would conclude that Macs are overpriced in the UK. I think I'll get mine elsewhere...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-308992736105091102?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/308992736105091102/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=308992736105091102' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/308992736105091102'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/308992736105091102'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/04/macbookpro-index.html' title='The MacbookPro Index'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_bXDgUoT7V7o/R_IlQ3pEvwI/AAAAAAAAAGc/Sp-6Rq1HaZQ/s72-c/MacDiff.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-6230491151259415119</id><published>2008-03-19T07:35:00.000-07:00</published><updated>2008-04-01T06:01:09.108-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Apple'/><category scheme='http://www.blogger.com/atom/ns#' term='Macbook Pro'/><title type='text'>The Mac is Back*</title><content type='html'>* No, not &lt;a href="http://www.economist.com/world/na/displaystory.cfm?story_id=10498767"&gt;John McCain&lt;/a&gt;, I'm talking about the Apple Mac.&lt;br /&gt;&lt;br /&gt;I'm going to make a wild prediction:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;The Apple Mac will increase it's market share by more than 50% over the next two years from 4.8% to at least 7.5 %&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;I'm basing my speculation on some figures:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.macworld.com/article/51932/2006/07/marketshare.html"&gt;Mac market share went up 16% year-on-year&lt;/a&gt; (Feb 2008. From 4.4 to 4.8% )&lt;/li&gt;&lt;li&gt;&lt;a href="http://arstechnica.com/journals/apple.ars/2008/03/18/apple-spanks-rest-of-computer-industry-in-february-sales"&gt;Macs represented 14 percent of sales last month&lt;/a&gt; (Feb 2008)&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.intomobile.com/2008/02/05/apple-iphone-keeps-getting-stronger-in-smartphone-segment.html"&gt;Solid iPhone market share figures in the US&lt;/a&gt; (28% in the US for Q4 2007)&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;And some opinion:&lt;br /&gt;&lt;br /&gt;First, &lt;a href="http://en.wikipedia.org/wiki/Halo_effect"&gt;the Halo effect&lt;/a&gt;. Apple now has a solid (well integrated) product offering with the Mac line, iPods, iPhones and iTunes (music and movies). Consumers who are exposed to any of these products have (mostly) good experiences, which means they get warm and fuzzy inside and buy more Apple products. (&lt;a href="http://arstechnica.com/journals/apple.ars/2008/01/28/steve-jobs-reassures-investors-employees-over-stock-drop"&gt;but NOT shares.&lt;/a&gt; Hmmmm). Why are their product doing so well despite their higher prices? My first axiom:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;People are always willing to pay more for a better quality product&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Secondly, some perception. I work in technology (creating software), and I would hope that we have a better idea of where the market is heading that the average consumer. I would also like to think that we have some influence over how people will be using their computers in the future, especially though the products and services that we create. I'm seeing more and more Macs being used by the creators of software, &lt;span style="font-style: italic;"&gt;especially by those who are more discerning&lt;/span&gt;,  and my perception is that there is an increasingly positive view of the Mac and Mac OS X in the amongst software creators. Couple that with the buzz that has been created by the iPhone and the iPhone SDK (which delivers a platform for writing software for the iPhone by 3rd party developers), and the future looks rosy.&lt;br /&gt;&lt;br /&gt;Lastly, Windows Vista. No matter what Microsoft says, it hasn't been a big success. All the problems associated with it's (much delayed) launch, the quality of the product, the whole "&lt;a href="http://seattlepi.nwsource.com/business/352442_vista23.html"&gt;Vista Capable&lt;/a&gt;" fiasco etc. has created a unique opportunity for Mac OS X.&lt;br /&gt;&lt;br /&gt;Let's wait and see...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-6230491151259415119?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/6230491151259415119/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=6230491151259415119' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/6230491151259415119'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/6230491151259415119'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/03/mac-is-back.html' title='The Mac is Back*'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-1712284182946334591</id><published>2008-03-06T06:44:00.000-08:00</published><updated>2008-04-01T06:01:38.417-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='IE'/><category scheme='http://www.blogger.com/atom/ns#' term='Safari'/><category scheme='http://www.blogger.com/atom/ns#' term='Opera'/><category scheme='http://www.blogger.com/atom/ns#' term='Acid3'/><category scheme='http://www.blogger.com/atom/ns#' term='Firefox'/><title type='text'>Acid3 Test released</title><content type='html'>What is &lt;a href="http://acid3.acidtests.org/"&gt;Acid3&lt;/a&gt;? From &lt;a href="http://en.wikipedia.org/wiki/Acid3"&gt;Wikipedia&lt;/a&gt;: "&lt;b&gt;Acid3&lt;/b&gt; is a test suite that checks how well a &lt;a href="http://en.wikipedia.org/wiki/Web_browser" title="Web browser"&gt;web browser&lt;/a&gt; follows certain &lt;a href="http://en.wikipedia.org/wiki/Web_standards" title="Web standards"&gt;web standards&lt;/a&gt;, especially relating to the &lt;a href="http://en.wikipedia.org/wiki/Document_Object_Model" title="Document Object Model"&gt;DOM&lt;/a&gt; and &lt;a href="http://en.wikipedia.org/wiki/JavaScript" title="JavaScript"&gt;JavaScript&lt;/a&gt;."&lt;br /&gt;&lt;br /&gt;There's some synopsis &lt;a href="http://www.drunkenfist.com/temp/acid3.html"&gt;here&lt;/a&gt; and on the wikipedia page&lt;br /&gt;&lt;br /&gt;Here are the results of the browsers that I have on my work machine (Windows XP). Note that this is a score out of 100:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_bXDgUoT7V7o/R9AKP4NB88I/AAAAAAAAAGE/9RLsdzhhoQc/s1600-h/Acid3.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_bXDgUoT7V7o/R9AKP4NB88I/AAAAAAAAAGE/9RLsdzhhoQc/s400/Acid3.png" alt="" id="BLOGGER_PHOTO_ID_5174647239750054850" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;No surprise that IE, the bastion of web standards, fails so miserably. I'm actually surprised that Firefox is doing better than Opera, since my subjective opinion at the moment is that Opera renders most pages better than Firefox (except the Google ones, which is not much of a surprise since Google &lt;a href="http://blogs.zdnet.com/BTL/?p=6715"&gt;funds a large chunk of Firefox development&lt;/a&gt;). Also, the first time I tried the test in Opera 9.26, it crashed the browser. And it just crashed again now with Acid3 open.&lt;br /&gt;&lt;br /&gt;All these 4 browsers have big releases coming up, so it remains to be seen who become the new king of the Acid hill. Webkit based browsers like Safari seems to be in the early lead with scores of 90/100 floating around...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-1712284182946334591?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/1712284182946334591/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=1712284182946334591' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/1712284182946334591'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/1712284182946334591'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/03/acid3-test-released.html' title='Acid3 Test released'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_bXDgUoT7V7o/R9AKP4NB88I/AAAAAAAAAGE/9RLsdzhhoQc/s72-c/Acid3.png' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-1614764708926264525</id><published>2008-03-05T01:58:00.000-08:00</published><updated>2008-03-05T06:05:29.573-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Yaws'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='Apache'/><category scheme='http://www.blogger.com/atom/ns#' term='FUSE'/><title type='text'>Apache + Erlang for dynamic web content</title><content type='html'>It dawned on me this morning that there is a way of using &lt;a href="http://www.apache.org/"&gt;Apache&lt;/a&gt; (or any other web server for that matter) with &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt; to create dynamic web content...&lt;br /&gt;&lt;br /&gt;How? Use &lt;a href="http://fuse.sourceforge.net/"&gt;FUSE&lt;/a&gt;. There is an implementation of FUSE for Erlang by the &lt;a href="http://dukesoferl.blogspot.com/"&gt;Dukes of Erl&lt;/a&gt;, called &lt;a href="http://code.google.com/p/fuserl/"&gt;fuserl&lt;/a&gt;. If you haven't heard of FUSE, it's a way to make anything you want look like a file system. By using fuserl, you can make an Mnesia database  (just one example) look like a file system. You can map queries to directories, and the files that are "listed" can contain the rows of your tables. But you can structure your filesystem in any way you want!&lt;br /&gt;&lt;br /&gt;Take for example &lt;a href="http://code.google.com/p/youtubefs/"&gt;YoutubeFS&lt;/a&gt;: "&lt;span style="font-style: italic;"&gt;YoutubeFS enables you to browse your favorite Youtube videos locally on your desktop without going to the youtube website. Just create a youtube account and add videos to your playlists, favorites list or subscribe to different channels. YoutubeFS then enables you to automatically load these videos to a local folder on your desktop. You can then view these videos (using a browser) as if they are local files.&lt;/span&gt;"&lt;br /&gt;&lt;br /&gt;If you extend this idea, you can imagine that you can point your Apache server to a FUSE filesystem location which generates the "contents" of the file system dynamically. To Apache it looks like it's serving normal files, but behind the scenes you can have a distributed Erlang system generating content for you...&lt;br /&gt;&lt;br /&gt;This wouldn't work so well if you actually want to use parameters in your http queries, but then I would use &lt;a href="http://yaws.hyber.org/"&gt;Yaws &lt;/a&gt;with an appmod if that's what you need.&lt;br /&gt;&lt;br /&gt;P.S. You don't have to use Erlang either, you can write FUSE file systems in many other languages...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-1614764708926264525?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/1614764708926264525/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=1614764708926264525' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/1614764708926264525'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/1614764708926264525'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/03/apache-erlang-for-dynamic-web-content.html' title='Apache + Erlang for dynamic web content'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-8714167263367784337</id><published>2008-02-25T10:25:00.000-08:00</published><updated>2008-02-27T01:48:39.540-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Yaws'/><category scheme='http://www.blogger.com/atom/ns#' term='Dayfindr.com'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>The time has come</title><content type='html'>I've been working on an Erlang project at home for a while, and it has reached the first public version! Here it is &lt;a href="http://www.dayfindr.com/"&gt;http://www.dayfindr.com.&lt;/a&gt; It's a simple utility that can make your life easier.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_bXDgUoT7V7o/R8QFTtqHpxI/AAAAAAAAAFU/6aVjiEqdGZU/s1600-h/dayfindr.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_bXDgUoT7V7o/R8QFTtqHpxI/AAAAAAAAAFU/6aVjiEqdGZU/s400/dayfindr.png" alt="" id="BLOGGER_PHOTO_ID_5171264108360083218" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;It ticks some of the boxes that Paul Graham related recently in &lt;a href="http://www.paulgraham.com/newthings.html"&gt;Six Principles for Making things:&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;(a) simple solutions &lt;span style="color: rgb(0, 153, 0); font-style: italic;"&gt;I hope so. This was one of the big design principles - to make it as simple as possible. That's why, for example, it leverages email instead of using some fancy social networking/address book features. There's also no login, since your email address already identifies you. &lt;a href="http://www.tripit.com/"&gt;TripIt &lt;/a&gt;uses the same idea&lt;/span&gt;&lt;br /&gt;(b) to overlooked problems &lt;span style="font-style: italic; color: rgb(0, 153, 0);"&gt;nothing that I know of addresses it&lt;br /&gt;&lt;/span&gt;(c) that actually need to be solved, and &lt;span style="font-style: italic; color: rgb(0, 153, 0);"&gt;This tries to help with  a real problem that I've encountered myself. Feedback on the idea has been quite positive, so I know it's something that people will find useful! &lt;/span&gt;&lt;br /&gt;(d) deliver them as informally as possible &lt;span style="font-style: italic; color: rgb(0, 153, 0);"&gt;Hope so&lt;/span&gt;&lt;br /&gt;(e) starting with a very crude version 1, then &lt;span style="font-style: italic; color: rgb(0, 153, 0);"&gt;Relatively crude&lt;/span&gt;&lt;br /&gt;(f) iterating rapidly. &lt;span style="font-style: italic; color: rgb(0, 153, 0);"&gt;We'll see!&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;It's implemented on a Lyme stack (Linux, &lt;a href="http://yaws.hyber.org/"&gt;Yaws&lt;/a&gt;, Mnesia and &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt;). This project has generated some interesting questions, that I will relate in a series of posts. For example:&lt;br /&gt;&lt;br /&gt;Why &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt;?&lt;br /&gt;Why not &lt;a href="http://erlyweb.org/"&gt;ErlyWeb&lt;/a&gt;?&lt;br /&gt;Why not &lt;a href="http://www.rubyonrails.org/"&gt;Rails&lt;/a&gt;?&lt;br /&gt;Why did I abandon the fully functional &lt;a href="http://en.wikipedia.org/wiki/Common_Lisp"&gt;Lisp&lt;/a&gt; version?&lt;br /&gt;&lt;br /&gt;So, use it, it's free. If you like it (or more importantly if you don't), lemme know at &lt;a href="mailto:feedback@dayfindr.com"&gt;feedback@dayfindr.com&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;P.S. To the Internet Explorer users: You will notice that there's a javascript error on the page, because some function is "not implemented", which means you'll see check boxes and not coloured boxes in the calendar. The current versions of &lt;a href="http://www.opera.com/"&gt;Opera&lt;/a&gt;, &lt;a href="http://www.mozilla.com/firefox/"&gt;Firefox&lt;/a&gt; and &lt;a href="http://www.apple.com/safari/"&gt;Safari&lt;/a&gt; have no problem with it. Maybe you should consider an upgrade...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-8714167263367784337?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/8714167263367784337/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=8714167263367784337' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/8714167263367784337'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/8714167263367784337'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/02/time-has-come.html' title='The time has come'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_bXDgUoT7V7o/R8QFTtqHpxI/AAAAAAAAAFU/6aVjiEqdGZU/s72-c/dayfindr.png' height='72' width='72'/><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-2965993212113587195</id><published>2008-02-22T01:33:00.001-08:00</published><updated>2008-02-23T08:19:41.747-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Mnesia'/><category scheme='http://www.blogger.com/atom/ns#' term='Yaws'/><category scheme='http://www.blogger.com/atom/ns#' term='Web server performance'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Lyme vs Lamp IV</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_bXDgUoT7V7o/R8BDExjNguI/AAAAAAAAAFE/gp1yKWLUuQg/s1600-h/lymevslamp_small.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_bXDgUoT7V7o/R8BDExjNguI/AAAAAAAAAFE/gp1yKWLUuQg/s400/lymevslamp_small.png" alt="" id="BLOGGER_PHOTO_ID_5170206121520890594" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The first &lt;a href="http://21ccw.blogspot.com/2008/02/lyme-vs-lamp-i.html"&gt;Lyme vs Lamp&lt;/a&gt; comparison is here!&lt;br /&gt;&lt;br /&gt;Let's recap. I have created a single web page, in Lyme and Lamp, based on database queries. The query is from a single table with 1000 "blog" entries, with Id, Timestamp, Title and Content fields. The web page displays the last 10 entries in the table.&lt;br /&gt;&lt;br /&gt;I use Tsung to generate users at a progressive rate, starting from 2 users per second up to 1000 users per second:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_bXDgUoT7V7o/R76XcBjNgrI/AAAAAAAAAEs/HooYKdChs4U/s1600-h/arrival.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_bXDgUoT7V7o/R76XcBjNgrI/AAAAAAAAAEs/HooYKdChs4U/s400/arrival.png" alt="" id="BLOGGER_PHOTO_ID_5169735929976160946" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Subjecting each web application stack to this test, they compare as follows:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_bXDgUoT7V7o/R76XhRjNgsI/AAAAAAAAAE0/RzNEaZRQ0AU/s1600-h/transaction-rate.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_bXDgUoT7V7o/R76XhRjNgsI/AAAAAAAAAE0/RzNEaZRQ0AU/s400/transaction-rate.png" alt="" id="BLOGGER_PHOTO_ID_5169736020170474178" border="0" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_bXDgUoT7V7o/R76XlhjNgtI/AAAAAAAAAE8/NgnDgT_fM1g/s1600-h/mean-transaction-time.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_bXDgUoT7V7o/R76XlhjNgtI/AAAAAAAAAE8/NgnDgT_fM1g/s400/mean-transaction-time.png" alt="" id="BLOGGER_PHOTO_ID_5169736093184918226" border="0" /&gt;&lt;/a&gt;But what does it mean?&lt;br /&gt;&lt;br /&gt;It looks like Lyme and Lamp perform the same up until 240 seconds into the simulations, which is where we step up from 66 to 100 requests per second. At about that point, Lyme starts to handle less transactions per second, and the mean transaction time increases dramatically.&lt;br /&gt;&lt;br /&gt;Where's the bottleneck? Good question. I suspect it's in the database query, but this is just a shot in the dark. Why could it be? Well, in the PHP version, it's very simple to retrieve the last 10 entries:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SELECT * FROM blog_entries ORDER BY `ID` DESC LIMIT 0 , 10&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Whereas with Mnesia, I'm not aware of a better way to retrieve the last 10 rows, other than to iterate backwards from the end of the table:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;last_N_entries(_Key, 0, Acc) -&gt;&lt;br /&gt;lists:reverse(Acc);&lt;br /&gt;last_N_entries(Key, N, Acc) -&gt;&lt;br /&gt;{atomic, {Entry, NextKey}} =&lt;br /&gt;mnesia:transaction(&lt;br /&gt;  fun() -&gt;&lt;br /&gt;       [Entry] = mnesia:read({blog_entry, Key}),&lt;br /&gt;       NextKey = mnesia:prev(blog_entry, Key),&lt;br /&gt;       {Entry, NextKey}&lt;br /&gt;  end),&lt;br /&gt;last_N_entries(NextKey, N-1, [Entry|Acc]).&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;last_N_entries(N) -&gt;&lt;br /&gt;{atomic, LastKey} = mnesia:transaction(fun() -&gt; mnesia:last(blog_entry) end),&lt;br /&gt;last_N_entries(LastKey, N, []).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I might try some different approaches to the Mnesia query (using ram copies instead of disk copies for example), but I'd rather put some effort into making the experiment a bit more focussed. I'd like to especially see what happens when multiple cores are thrown at the problem, but &lt;span style="font-weight: bold;"&gt;more importantly&lt;/span&gt;, what happens when the transactions become concurrent.&lt;br /&gt;&lt;br /&gt;I have now realised that this is scenario is &lt;span style="font-weight: bold;"&gt;essentially a sequential load test&lt;/span&gt;, which will not show up Lyme's strengths. The pages render rather quickly, so there is no concurrent page rendering. Thus, I think the next step should be to create another scenario that will create concurrent page requests...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-2965993212113587195?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/2965993212113587195/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=2965993212113587195' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/2965993212113587195'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/2965993212113587195'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/02/lyme-vs-lamp-iv.html' title='Lyme vs Lamp IV'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_bXDgUoT7V7o/R8BDExjNguI/AAAAAAAAAFE/gp1yKWLUuQg/s72-c/lymevslamp_small.png' height='72' width='72'/><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-7679688330824356587</id><published>2008-02-19T05:08:00.000-08:00</published><updated>2008-02-19T10:49:13.935-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Mnesia'/><category scheme='http://www.blogger.com/atom/ns#' term='Yaws'/><category scheme='http://www.blogger.com/atom/ns#' term='Web server performance'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Lyme vs Lamp III</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_bXDgUoT7V7o/R7rWPRjNgqI/AAAAAAAAAEk/48XuBGQk9-Q/s1600-h/lymevslamp_small.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_bXDgUoT7V7o/R7rWPRjNgqI/AAAAAAAAAEk/48XuBGQk9-Q/s400/lymevslamp_small.png" alt="" id="BLOGGER_PHOTO_ID_5168679080258536098" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://21ccw.blogspot.com/2008/02/lyme-vs-lamp-i.html"&gt;Lyme vs Lamp&lt;/a&gt; continues...&lt;br /&gt;&lt;br /&gt;I've been spending some time figuring out how &lt;a href="http://tsung.erlang-projects.org/"&gt;Tsung&lt;/a&gt; outputs the data, how &lt;a href="http://www.gnuplot.info/"&gt;gnuplot &lt;/a&gt;works and how to create my own graphs using the Tsung output data. I now have a graph that shows the throughput rate (Kbits per second) and the arrival rate of users.&lt;br /&gt;&lt;br /&gt;When you set up the testing scenarios using Tsung, you can specify the arrival rate of new users. I've set up a simple progression, that changes every 60 seconds, going from 2 users per second up to 1000 users per second. You can see the progression in the graph below.&lt;br /&gt;&lt;br /&gt;The page that is being requested is generated from an Mnesia database, with Yaws. The database contains 1000 made-up blog "postings", and the page requested renders the last 5 postings. The machine is quite old, a 2004 laptop actually, and Tsung is also running on the same machine as Lyme.&lt;br /&gt;&lt;br /&gt;I've plotted the data throughput rate against  the arrival rate. (I've used &lt;a href="http://www.inkscape.org/"&gt;Inkscape&lt;/a&gt; to polish the gnuplot SVG output. I really love it and use it all the time). Here's the result:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_bXDgUoT7V7o/R7rVnBjNgpI/AAAAAAAAAEc/1VMKu5SvgHM/s1600-h/pretty-throughput.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_bXDgUoT7V7o/R7rVnBjNgpI/AAAAAAAAAEc/1VMKu5SvgHM/s400/pretty-throughput.png" alt="" id="BLOGGER_PHOTO_ID_5168678388768801426" border="0" /&gt;&lt;/a&gt;As you can see, the throughput rate increases proportionally to the user arrival rate. The "server" manager to handle 200 users/sec, but with 500 requests per second the increase in rate is not proportional. Looks like the max has been reached. Increasing the request rate to 1000 requests shows no difference in throughput. Please comment if you have any more insight into the data.&lt;br /&gt;&lt;br /&gt;I should be able to get a PHP version soon, so I can compare it against something...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-7679688330824356587?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/7679688330824356587/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=7679688330824356587' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/7679688330824356587'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/7679688330824356587'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/02/lyme-vs-lamp-iii.html' title='Lyme vs Lamp III'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_bXDgUoT7V7o/R7rWPRjNgqI/AAAAAAAAAEk/48XuBGQk9-Q/s72-c/lymevslamp_small.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-2969310866781400652</id><published>2008-02-14T10:29:00.000-08:00</published><updated>2008-02-19T10:49:40.590-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Mnesia'/><category scheme='http://www.blogger.com/atom/ns#' term='Yaws'/><category scheme='http://www.blogger.com/atom/ns#' term='Web server performance'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Lyme vs Lamp II - The first graph arrives</title><content type='html'>The first graph of the &lt;a href="http://21ccw.blogspot.com/2008/02/lyme-vs-lamp-i.html"&gt;Lyme vs Lamp&lt;/a&gt; debate has arrived!&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_bXDgUoT7V7o/R7SMexjNgoI/AAAAAAAAAEU/RJos1uCc2fs/s1600-h/graphes-Size-rate.png"&gt;&lt;br /&gt;&lt;br /&gt;&lt;img src="http://3.bp.blogspot.com/_bXDgUoT7V7o/R7SMexjNgoI/AAAAAAAAAEU/RJos1uCc2fs/s400/graphes-Size-rate.png" alt="" id="BLOGGER_PHOTO_ID_5166909132825789058" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;What does it mean? It means that I've got the LYME stack working with a prototype mnesia-backed web page, and Tsung is doing something. But that's about it for now, the actual results are almost irrelevant...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-2969310866781400652?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/2969310866781400652/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=2969310866781400652' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/2969310866781400652'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/2969310866781400652'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/02/lyme-vs-lamp-ii-first-graph-arrives.html' title='Lyme vs Lamp II - The first graph arrives'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_bXDgUoT7V7o/R7SMexjNgoI/AAAAAAAAAEU/RJos1uCc2fs/s72-c/graphes-Size-rate.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-4131026676001001998</id><published>2008-02-14T06:01:00.001-08:00</published><updated>2008-04-04T02:13:42.539-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Mnesia'/><category scheme='http://www.blogger.com/atom/ns#' term='Yaws'/><category scheme='http://www.blogger.com/atom/ns#' term='Web server performance'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Lyme vs Lamp I</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_bXDgUoT7V7o/R7RRZxjNgmI/AAAAAAAAAEE/Q1KvUvVFyKQ/s1600-h/lymevslamp.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_bXDgUoT7V7o/R7RRZxjNgmI/AAAAAAAAAEE/Q1KvUvVFyKQ/s400/lymevslamp.png" alt="" id="BLOGGER_PHOTO_ID_5166844175740404322" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;As part of a presentation at &lt;a href="http://www.spaconference.org/spa2008/sessions/session159.html"&gt;SPA2008 &lt;/a&gt;that I'm involved in, I'm doing a bit of load testing on Lamp and Lyme. &lt;a href="http://en.wikipedia.org/wiki/LAMP_%28software_bundle%29"&gt;LAMP &lt;/a&gt;is Linux + Apache + MySql + PHP, and LYME is Linux + &lt;a href="http://yaws.hyber.org/"&gt;Yaws &lt;/a&gt;+ &lt;a href="http://www.erlang.org/doc/apps/mnesia/index.html"&gt;Mnesia &lt;/a&gt;+ Erlang. (Mnesia is the Erlang database, Yaws is the Erlang web server).&lt;br /&gt;&lt;br /&gt;Interestingly, it would seem that in community there is now a precedent to name erlang projects after diseases/conditions, since &lt;a href="http://en.wikipedia.org/wiki/Yaws"&gt;yaws &lt;/a&gt;and &lt;a href="http://en.wikipedia.org/wiki/Lyme_disease"&gt;lyme&lt;/a&gt; are both diseases. AND mnesia used to be called  &lt;a href="http://en.wikipedia.org/wiki/Amnesia"&gt;amnesia&lt;/a&gt;. Maybe I'll develop a killer app in Erlang called &lt;a href="http://en.wikipedia.org/wiki/Ankylosing_spondylitis"&gt;ankylosing spondylitis&lt;/a&gt;. Just kidding.&lt;br /&gt;&lt;br /&gt;So, there is a rather well-known &lt;a href="http://www.sics.se/%7Ejoe/apachevsyaws.html"&gt;comparison &lt;/a&gt;of Apache and Yaws, but I'd like to go a step further. I'd like to know how a complete web application stack performs under load testing. As an initial comparison, I'll do Lyme and Lamp, and then move on to some others. I would like to have 3 scenarios, a static page only, a dynamic page and a dynamic page with a database backend.&lt;br /&gt;&lt;br /&gt;Exactly how to construct these scenarios are still unclear, so suggestions are welcome.&lt;br /&gt;&lt;br /&gt;Why test? Why yet another performance benchmark?&lt;br /&gt;- Because it's easy. It's much easier to test something (relatively) objectively, and then wave the results in the air to prove your point. It's much harder to debate the merits of technical choices in the real world, where we have constraints such as budget, skills availability, culture, vested interests etc.&lt;br /&gt;- It creates conversation. Which is a good thing. No flame wars please.&lt;br /&gt;&lt;br /&gt;I'll be using Ubuntu 7.10 and &lt;a href="http://tsung.erlang-projects.org/"&gt;Tsung &lt;/a&gt;and will publish everything, including configuration file, source files etc. etc.&lt;br /&gt;&lt;br /&gt;Updates:&lt;br /&gt;&lt;a href="http://21ccw.blogspot.com/2008/02/lyme-vs-lamp-ii-first-graph-arrives.html"&gt;Lyme vs Lamp II - The first graph arrives&lt;/a&gt;&lt;br /&gt;&lt;a href="http://21ccw.blogspot.com/2008/02/lyme-vs-lamp-iii.html"&gt;Lyme vs Lamp III&lt;/a&gt;&lt;br /&gt;&lt;a href="http://21ccw.blogspot.com/2008/02/lyme-vs-lamp-iv.html"&gt;Lyme vs Lamp IV&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-4131026676001001998?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/4131026676001001998/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=4131026676001001998' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/4131026676001001998'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/4131026676001001998'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/02/lyme-vs-lamp-i.html' title='Lyme vs Lamp I'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_bXDgUoT7V7o/R7RRZxjNgmI/AAAAAAAAAEE/Q1KvUvVFyKQ/s72-c/lymevslamp.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-5536478511629832885</id><published>2008-02-11T02:36:00.000-08:00</published><updated>2008-02-11T10:34:35.055-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Observer Pattern'/><category scheme='http://www.blogger.com/atom/ns#' term='Visitor pattern'/><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><title type='text'>The Visitor Pattern eliminates enums from the Observer Pattern</title><content type='html'>To avoid boolean parameters, every now and then in a moment of weakness I do something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public void addFile(File file, InputOutput inputOrOutput) {&lt;br /&gt;  switch(inputOrOutput) {&lt;br /&gt;  case InputOrOutput.INPUT: ... break;&lt;br /&gt;  case InputOrOutput.OUTPUT: ... break;&lt;br /&gt;  default: assert false;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Which is not a very good solution. There are at least two problems:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;I have to maintain an enum&lt;/li&gt;&lt;br /&gt;&lt;li&gt;There's a possibility that the parameter is null, which creates an opportunity for an error condition&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;I can achieve the same result by using two functions:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public void addInputFile(File file) {&lt;br /&gt;  ...&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public void addOutputFile(File file) {&lt;br /&gt;  ...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This solves the two problems and and also satisfies the readability requirement.&lt;br /&gt;&lt;br /&gt;How does this relate to &lt;a href="http://en.wikipedia.org/wiki/Observer_pattern"&gt;Observers&lt;/a&gt;? When I use the Java Observer interface and Observable class, I usually end up with a class defining the chunk of data that describes the change. This chunk is the delta of the object state from the last notification. And a object of this class is received by the observers.&lt;br /&gt;&lt;br /&gt;This delta class can take this form:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public class ModelObservedData {&lt;br /&gt;&lt;br /&gt;    enum Type { ADDED, REMOVED };&lt;br /&gt;        &lt;br /&gt;    // Members that contain the data&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;    public ModelObservedData (Type type, Data data) {&lt;br /&gt;        ...&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Type getType() {&lt;br /&gt;        ...&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Data getData() {&lt;br /&gt;        ...&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and then in the observer&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;    update(Observable o, Object arg) {&lt;br /&gt;        // Assert on observable source and type or use an if when observing&lt;br /&gt;        // multiple sources&lt;br /&gt;&lt;br /&gt;        ModelObservedData a data = (ModelObservedData)arg;&lt;br /&gt;        switch(data.getType()) {&lt;br /&gt;        case ADDED: doAdded(data.getData()) break;&lt;br /&gt;        case REMOVED: doRemoved(data.getData()) break;&lt;br /&gt;        default: assert false;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I don't find this very elegant. Now, considering the whole &lt;a href="http://21ccw.blogspot.com/2008/01/single-dispatch-multiple-dispatch-and.html"&gt;multiple dispatch and visitor pattern&lt;/a&gt; that I wrote about, there is a more elegant solution.&lt;br /&gt;&lt;br /&gt;Essentially, you visit the observed data instead of getting the type of the delta and switching your implementation on the type. The observed data now takes this form:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class ModelObservedData {&lt;br /&gt;&lt;br /&gt;    ... &lt;br /&gt;&lt;br /&gt;    public static interface IVisitor {&lt;br /&gt;&lt;br /&gt;        void dataAdded(Data data);&lt;br /&gt;        void dataRemoved(Data data);&lt;br /&gt;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void accept(IVisitor visitor) {&lt;br /&gt;        if (...) {&lt;br /&gt;            visitor.dataAdded(addedData);&lt;br /&gt;        } else if (...) {&lt;br /&gt;            visitor.dataRemoved(removedData);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The client implements the IVisitor interface and the update with the switch becomes an acceptance.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;    update(Observable o, Object arg) {&lt;br /&gt;        // Assert on observable source and type or ifs when observing&lt;br /&gt;        // multiple sources&lt;br /&gt;        ModelObservedData  data = (ModelObservedData)arg;&lt;br /&gt;        data.accept(this);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    void dataAdded(Data data) {&lt;br /&gt;        ...&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    void dataRemoved(Data data) {&lt;br /&gt;        ...&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This mechanism doesn't have the switch in the observer, there's no more enum, and the observed data can better encapsulate the state that defines the delta.  &lt;br /&gt;&lt;br /&gt;The last bit would be to eliminate constructors of the observed data that are difficult to read. For example, you would have one constructor that receives two data parameters, the added and removed data, and stores them as fields. But then the client needs to know the ordering, and have null values in the constructor. Instead, make that constructor private, and create two factory methods that describe the construction process better:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class ModelObservedData {&lt;br /&gt;&lt;br /&gt;    Data added;&lt;br /&gt;    Data removed;&lt;br /&gt;&lt;br /&gt;    public static ModelObservedData createAdded(Data data)        &lt;br /&gt;        return new ModelObservedData (data, null /* removed */);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    public static ModelObservedData createRemoved(Data data) {&lt;br /&gt;        return new ModelObservedData (null /* added */, data);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private ModelObservedData (Data dataAdded, Date dataRemoved) {&lt;br /&gt;        this.dataAdded = dataAdded;&lt;br /&gt;        this.dataRemoved = dataRemoved;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You now have a more elegant solution (I think), with less possibility for error states and no enum to maintain. Also, the switching that each client would have to implement is now implemented only once in the observed class, so there is less duplication.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-5536478511629832885?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/5536478511629832885/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=5536478511629832885' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/5536478511629832885'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/5536478511629832885'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/02/visitor-pattern-eliminates-enums-from.html' title='The Visitor Pattern eliminates enums from the Observer Pattern'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-3036641107254106725</id><published>2008-01-24T05:22:00.000-08:00</published><updated>2008-01-24T06:27:47.209-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Visitor pattern'/><category scheme='http://www.blogger.com/atom/ns#' term='single dispatch'/><category scheme='http://www.blogger.com/atom/ns#' term='multiple dispatch'/><title type='text'>Single Dispatch, Multiple dispatch and the Visitor Pattern</title><content type='html'>Every now and then the question of the &lt;a href="http://en.wikipedia.org/wiki/Visitor_pattern"&gt;Visitor pattern&lt;/a&gt; comes up, and sometimes we forget why we need the visitor pattern in the first place! The Visitor pattern is a way to implement double dispatch in &lt;a href="http://en.wikipedia.org/wiki/Single_dispatch"&gt;single dispatch&lt;/a&gt; languages like C++ and Java.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic; font-weight: bold;" &gt;Single Dispatch&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The best method that I've discovered for remembering what single dispatch is, is by relating it to &lt;span style="font-weight: bold;"&gt;compile time&lt;/span&gt; vs. &lt;span style="font-weight: bold;"&gt;runtime &lt;/span&gt;function resolution, or &lt;span style="font-weight: bold;"&gt;overloading &lt;/span&gt;vs. &lt;span style="font-weight: bold;"&gt;overriding&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;When you do overloading, the function (or method) that will be executed within the calling context is determined at compile time. Overriding is the ability to choose the method to execute at runtime (it's called single dispatch  since you can only use one parameter to the function to determine which one to execute, which is the implicit "this" parameter in curly bracket languages).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; font-style: italic;" &gt;Example&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I'll illustrate with an example. Let's say that you have application that manages you share portfolio. You have two types of shares, a normal share and a preference share, and you wish to generate a report on the value of your portfolio. The value of a preference share is calculated differently to that of a normal share.&lt;br /&gt;&lt;br /&gt;In Java, we'll have something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;class Share {&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;class PreferenceShare extends Share {&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;class PortfolioWriter {&lt;br /&gt;&lt;br /&gt;   public void write(Share share) {&lt;br /&gt;      System.out.println("Share");&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public void write(PreferenceShare preferenceShare) {&lt;br /&gt;      System.out.println("PreferenceShare");&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;public class SingleDispatch {&lt;br /&gt;&lt;br /&gt;   public static void main(String[] args) {&lt;br /&gt;&lt;br /&gt;      PortfolioWriter writer = new PortfolioWriter();&lt;br /&gt;&lt;br /&gt;      Share share = new Share();&lt;br /&gt;      writer.write(share);&lt;br /&gt;&lt;br /&gt;      PreferenceShare preferenceShare = new PreferenceShare();&lt;br /&gt;      writer.write(preferenceShare);&lt;br /&gt;&lt;br /&gt;      Share shareReferenceToPreferenceShare = new PreferenceShare();&lt;br /&gt;      writer.write(shareReferenceToPreferenceShare); // 1&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And here's the output:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$ java SingleDispatch&lt;br /&gt;Share&lt;br /&gt;PreferenceShare&lt;br /&gt;Share&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Looking at line marked with "// 1" in the code:&lt;br /&gt;The method that is called on the writer object is determined at compile time, and at compile time the parameter is a Share reference. It's not possible for the compiler to know that the reference actually point to an instance of PreferenceShare!&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic; font-weight: bold;" &gt;Lisp&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In Lisp, you have multiple dispatch, so you can do something like this&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(defgeneric write (generator instrument))&lt;br /&gt;&lt;br /&gt;(defmethod write ((g portfolio-generator) (s share))&lt;br /&gt;(format t "portfolio-generator: a share"))&lt;br /&gt;&lt;br /&gt;(defmethod write((g portfolio-generator) (p preference-share))&lt;br /&gt;(format t "portfolio-generator: a preference share"))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And when you call "(write ...)" with a report-generator instance and either a share or preference-share instance, you will get the expected result!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;In fact, in Lisp, if you have two (or more) type hierarchies you can choose to specialize the method at &lt;span style="font-weight: bold;"&gt;any &lt;/span&gt;point in the hierarchy of &lt;span style="font-weight: bold;"&gt;any &lt;/span&gt;of the parameters. This is very powerful, and the C++/Java object model looks like Lisp's poor cousin in comparison.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic; font-weight: bold;" &gt;Conclusion&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;You can imagine the limitation of single dispatch when you have a collection of objects that conform to some interface. If you want to do something on each instance (like generate a report), but you don't want to inject that logic into each class, you have to use a visitor pattern to get around the single dispatch limitation.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic; font-weight: bold;" &gt;Wikipedia references&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Visitor_pattern"&gt;Visitor Pattern&lt;/a&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Single_dispatch"&gt;Single Dispatch&lt;/a&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Multiple_dispatch"&gt;Multiple Dispatch&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-3036641107254106725?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/3036641107254106725/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=3036641107254106725' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/3036641107254106725'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/3036641107254106725'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/01/single-dispatch-multiple-dispatch-and.html' title='Single Dispatch, Multiple dispatch and the Visitor Pattern'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-7474312384030920954</id><published>2008-01-23T10:19:00.000-08:00</published><updated>2008-02-19T10:50:13.152-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Eclipse RCP'/><title type='text'>Eclipse RCP - Customizing the context menu in the Common Navigator</title><content type='html'>When you use the Common Navigator as a way to navigate your project, some of your book and web sources will indicate that you should include org.eclipse.ui.navigator.resources.* in you viewer's action binding. This adds all the actions of the common navigator to the context menu. You'll observe that there are quite a few. To see which action providers are included:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Add the Plug-ins view:  Window -&gt; Show View -&gt; Other -&gt; PDE -&gt; Plug-ins&lt;br /&gt;&lt;/li&gt;&lt;li&gt;In the Plug-ins view, navigate to &lt;span&gt;org.eclipse.ui.navigator.resources&lt;/span&gt; and double-click it, which will open the plug-in editor.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Go to the "Extensions" page.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Open&lt;span&gt; org.eclipse.ui.navigator.navigatorContent&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_bXDgUoT7V7o/R5eFzlJgk7I/AAAAAAAAADg/yOikCJYv3fk/s1600-h/navigatorActionBindings.png"&gt;&lt;img src="http://4.bp.blogspot.com/_bXDgUoT7V7o/R5eFzlJgk7I/AAAAAAAAADg/yOikCJYv3fk/s400/navigatorActionBindings.png" alt="" id="BLOGGER_PHOTO_ID_5158739019368010674" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now you'll see 6 Action Providers listed, &lt;span&gt;org.eclispe.ui.internal.navigator.resources.actions.OpenActionProvider&lt;/span&gt;,&lt;span&gt; ...actions.NewActionProvider&lt;/span&gt; etc, and selecting each will show it's id on the right hand side, e.g. &lt;span&gt;org.eclipse.ui.navigtor.resources.OpenActions&lt;/span&gt; for the OpenActionProvider. These are the ids you should use instead of &lt;span&gt;…resources.*&lt;/span&gt; in your own plug-in's navigator viewer action bindings to select the ones you want. Say, for example you want all of them except the New provider (because you have your own New con-text menu), your extensions view should look something like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_bXDgUoT7V7o/R5eGS1Jgk8I/AAAAAAAAADo/xiq8Z_oO7Vg/s1600-h/noNewInContextMenu.png"&gt;&lt;br /&gt;&lt;img src="http://1.bp.blogspot.com/_bXDgUoT7V7o/R5eGS1Jgk8I/AAAAAAAAADo/xiq8Z_oO7Vg/s400/noNewInContextMenu.png" alt="" id="BLOGGER_PHOTO_ID_5158739556238922690" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Voila! No more New menu!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-7474312384030920954?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/7474312384030920954/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=7474312384030920954' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/7474312384030920954'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/7474312384030920954'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/01/customizing-context-menu-in-common.html' title='Eclipse RCP - Customizing the context menu in the Common Navigator'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_bXDgUoT7V7o/R5eFzlJgk7I/AAAAAAAAADg/yOikCJYv3fk/s72-c/navigatorActionBindings.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-8391936201008185793</id><published>2008-01-23T10:08:00.000-08:00</published><updated>2008-02-19T10:50:04.125-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Eclipse RCP'/><title type='text'>Eclipse RCP - Where is my editor?</title><content type='html'>Is you Eclipse plugin/ECP editor not displaying? Have you spend hours checking and rechecking and poring through books and the internet and you're still frustrated?&lt;br /&gt;&lt;br /&gt;The solution might be simple! If you don't have an icon specified for the editor, it may not display. The plugin.xml editor doesn't indicate that it's a required field, so I'm assuming it's a bug. A VERY frustrating bug...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-8391936201008185793?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/8391936201008185793/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=8391936201008185793' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/8391936201008185793'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/8391936201008185793'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2008/01/where-is-my-editor.html' title='Eclipse RCP - Where is my editor?'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-7747635890337380468</id><published>2007-10-01T06:31:00.000-07:00</published><updated>2008-01-25T02:35:19.712-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Eclipse RCP'/><title type='text'>Eclipse RCP - Where are my errors?</title><content type='html'>By default, Eclipse swallows up most error messages and exceptions generated by the platform. During development though, it is advisable to enable the console log so the errors messages and exceptions can be seen. To enable the console log, “-consoleLog” should be added to the program arguments of the run configuration:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_bXDgUoT7V7o/RwD29z79YDI/AAAAAAAAABs/20hbwOVSp_8/s1600-h/consoleLog.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_bXDgUoT7V7o/RwD29z79YDI/AAAAAAAAABs/20hbwOVSp_8/s400/consoleLog.png" alt="" id="BLOGGER_PHOTO_ID_5116360718467031090" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;For each configuration, the program arguments can be changed via the "Arguments" tab on the Run dialog, accessible via the “Run…” menu or the run icon. You should add this for each run configuration.&lt;br /&gt;&lt;br /&gt;That's it! The bad news is that now you'll see the nasty errors and exceptions that your application is generating. The good news is that that you're aware of them, you'll be able to track them down, fix them, and improve the quality of your product...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-7747635890337380468?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/7747635890337380468/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=7747635890337380468' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/7747635890337380468'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/7747635890337380468'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2007/10/where-are-my-errors.html' title='Eclipse RCP - Where are my errors?'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_bXDgUoT7V7o/RwD29z79YDI/AAAAAAAAABs/20hbwOVSp_8/s72-c/consoleLog.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7435412383881764254.post-8845183073255541895</id><published>2007-10-01T06:28:00.001-07:00</published><updated>2008-01-23T10:18:55.003-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Eclipse RCP'/><title type='text'>Hello the Camp</title><content type='html'>As an inaugural post I'll describe what is likely to happen here. This blog will contain entries on my professional work at &lt;a href="http://www.zuehlke.com/en/"&gt;Zuhlke Engineering&lt;/a&gt;, where I'm currently working on an Eclipse RCP project. I'll be contributing some knowledge back to the community, mostly simple how-to's and pitfalls of the framework.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7435412383881764254-8845183073255541895?l=21ccw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://21ccw.blogspot.com/feeds/8845183073255541895/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7435412383881764254&amp;postID=8845183073255541895' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/8845183073255541895'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7435412383881764254/posts/default/8845183073255541895'/><link rel='alternate' type='text/html' href='http://21ccw.blogspot.com/2007/10/hello-camp-and-disclaimer.html' title='Hello the Camp'/><author><name>Benjamin Nortier</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
