The Arc Challenge in Ruby
There’s been a bit of buzz again in the last couple days about the Arc Challenge in our corner of the interwebs, since a Haskell entry was thrown into the ring. Since we’re big Sinatra users (TweetRex, another internal demo and our just-about-to-come-out new account manager are all Sinatra), I thought I’d give it a whirl to see how it stacks up.
I did this first in the comments on news.yc, but then kept playing with it a bit, so I thought I’d give an expanded version here.
First let’s start with a version that works, without any extra massaging (line broken to avoid scrollbar):
require 'rubygems' require 'sinatra' enable :sessions get '/' do session[:said] || '<form method="post"><input name="said" type="text" /><input type="submit" /> </form>' end post '/' do session[:said] = params[:said] '<a href="/">click here</a>' end |
The ugliness, relative to the Arc version is in the long HTML strings, but it’s certainly fairly concise, and also fairly readable.
The thing is, when doing web coding, most modern frameworks separate the presentation code from the application logic — and rightly so. This allows for the two to be refactored relatively independently. So, for the most part, I don’t see this missing logic in the Ruby core as much of an issue. However, in things like Rails’ ActionView, there are helpers that add in some some of the Arc-like constructs. I thought I’d write a few of my own, with concision in mind, to get an idea of what this sort of application could look like, and to give a better idea of how Ruby as a language stacks up to Lispy-concision.
Here I define a few convenience functions that we’ll pretend is our library.
require 'rubygems' require 'sinatra' enable :sessions # "Library" def element(name, params = {}) children = block_given? ? (Array.new << yield).flatten : nil "<#{name} " + params.keys.inject('') { |v, k| v += "#{k}=\"#{params[k]}\" " } + (children ? ('>' + children.join('') + "</#{name}>") : '/>') end def input(type, name = '', params = {}) element(:input, params.merge(:type => type, :name => name)) end def form(params = {}) params[:method] = :post if params[:method].nil? element(:form, params) { yield } end def link(target, params = {}) element(:a, params.merge(:href => target)) { yield } end # Implementation get '/' do session[:said] || form {[ input(:text, :said), input(:submit) ]} end post '/' do session[:said] = params[:said] link('/') { 'click me' } end |
(Before the Rubyists come out of the wood-work, I’ll note that I’m only so-so with Ruby, really we’re a C++ shop, and I wouldn’t really write an “element” function like that if I weren’t going OCD on concision.)
I tried to skimp on symbols by using blocks rather than a hash parameter to element, and could have saved another one by folding input(:submit) into its own function, but meh. So then the question comes, how does this stack up parse tree-wise? To do a comparison, I translated the “implementation” block there into pseudo-lisp:
(get '/' (or (hash 'said' session) (form (input 'text' 'said') (input 'submit')))) (post '/' (setq (hash 'said' session) (params 'said')) (link '/' 'click me'))) |
I count 34 items, per Paul’s metric. The irony is that the version with the long HTML strings naturally has less, weighing in at 23 entries, on par with the original Arc implementation.
Why I don’t buy into the comparison
At the end of the day though, while fun, I still find the comparison rather contrived. As stated, I don’t want presentation code in my application logic. If I turn to a real Sinatra application that we’ve done, notably soon-to-go-live account manager, here’s the line count:
Erik: /Users/scott/Projects/DirectedEdge/AccountManager> wc -l account.rb directed_edge_admin.rb account_manager.rb views/index.haml views/created.haml 121 account.rb 52 directed_edge_admin.rb 136 account_manager.rb 4 views/index.haml 16 views/created.haml 329 total
The problem is that the Arc Challenge is set up to what Paul calls the “Hello, world.” of web applications, but effectively it becomes a standard library comparison more than a language comparison. In practice, even for our trivial little account manager, we connect to three different web services APIs (our billing provider, our in-house Rails CRM and the administrative API of our recommendations web service), parse our modify our HTML templates using Nakagiri’s CSS selectors and then merge that with some Haml code. Were we to consider things like XML / JSON parsing / generation “core” features of a modern web programming framework, the scales would be tipped in the favor of more established web languages.
I think this is why I personally tend to drift from language to language based on the the problem and constraints currently under consideration. We literally have C++, Java, Ruby, Perl, Javascript, PHP, Python, C# and a wee bit of Scala in our repository, roughly in that order (the last four only for our language bindings), all being thrown at problems they’re well suited to. I believe an unintended moral of Paul’s example is that a language is only as good as how simple it makes tasks at hand, and in practice, things like available libraries weigh heavily into that.
markus:
“language is only as good as how simple it makes tasks at hand”
Well said.
December 20, 2009, 1:30 pm