Erudition and Inanity

brian guthrie blogs

Let's make a deal: Handshake, a contract system for Ruby

Posted by Brian Guthrie Tue, 20 Mar 2007 16:38:00 GMT

Handshake is an informal design by contract system written in pure Ruby. It’s designed to offer simple, flexible semantics for improving the clarity and robustness of your code. It supports three basic kinds of checking: invariants, method pre/post conditions, and method arguments. This is a unit-tests-pass-but-you-probably-shouldn’t-use-it-for-anything-important 0.1 release.

A couple of clarifications: Pre- and post-conditions, and argument contracts, all accept the name of the method you’d like them to apply to as an optional first argument. If you omit it, they’ll be applied to the next method you define.

Also, anything is a clause that always returns true.

Updated 5/9/07: I’ve modified some of the content below to reflect changes to Handshake’s interface. Handshake is also now obtainable as a gem (gem install handshake).

Argument Contracts

Argument contracts are insanely handy. They allow you to specify conditions for the arguments and return values of methods simply and easily. Here’s an example:

class AcceptsString
  include Handshake
  contract [ String, Fixnum ] => anything
  def initialize(str, num); ...; end
end

Wait a second, I hear you say. That’s not exciting at all! I just left Java behind forever. Quasi-statically-typed languages are for suckers.

Here’s the rub: Handshake accepts as an argument clause any object that implements the === method. Why? Because it’s an incredibly convenient “is equivalent to” form of comparison. It’s what makes Ruby’s case statement so handy. Try this awkwardly contrived example on for size:

# Rate a website on a scale from 1 to 5.
class WebsiteRatingSystem
  include Handshake

  contract [ String, /^http:\/\// ] => anything
  def initialize(name, url)
    @name, @url, @ratings = name, url, []
  end

  contract 1..5 => anything
  def add_rating(n); @ratings << n; end

  contract nil => 1..5
  def average_rating
    ratings_sum = @ratings.inject(0.0) { |sum, n| sum + n }
    ratings_sum / @ratings.length
  end

  contract [ String, [ Integer ], Block([String, Integer] => anything) ] => Hash
  def generate_report(name, *metrics, &block)
    ..
  end
end

But Brian, you say, in the imaginary conversation I am conducting with you in my head. Brian, I like being able to give it a range, or a regexp. That seems downright neato. But man, what a hassle to have to craft my own ===-implementing object for every single clause.

As it turns out, I have made this process entirely hassle-free for you.

contract assert("equals foo") { |arg| arg == "foo" } => anything
contract any?(String, Integer) => anything
contract [ all?( hash_of?(Symbol, String), 
                 hash_with_keys(:foo, :bar, :baz) ] => anything
contract nonzero? => hash_contract(:count => Integer, :description => String)
contract responds_to?(:each, :map, :length) => anything
contract nil => String

And they said that duck typing and contracts didn’t mix! Good day, sir and/or madam.

Except for also

I’ve defined some accessors, just for you.

class HoldsSomeThings
  include Handshake
  contract_accessor :foo => String, :bar => Integer
  contract_reader ...
  contract_writer ... # You get the idea.
end

Invariants

Also handy are invariants, which are conditions that should always hold. They’re checked after your object is constructed and before and after every method invocation. They have access to local methods and instance variables. They are all up in your class, policing your object.

class NonEmptyArray < Array
  include Handshake
  invariant { not empty? }
end

Pre- and post-conditions

Sometimes you need a broader, more holistic approach to method checking. These conditions, if specified, are executed before and after (respectively) the named method and have access to its arguments, return value(s), and any local methods and instance variables. These work very much like you’d expect them to:

class HoldOnJustAMinute
  include Handshake

  before("halt in the name of the law") do |arg|
    assert @justice_dispensable
    assert arg.law_abiding?
  end; def do_something_potentially_damaging(arg)
    ...
  end
end

Caveats

  • It isn’t underpinned by a formal calculus (although it’s modeled very loosely on the PLT Scheme contract library and on the Eiffel contract system). I’m more or less cool with that; Ruby’s appeal to me comes not least from its loose informalism. But the professor under whose grudging tutelage I developed this would like you to know that until it does it’s not good for anything whatsoever, and whatever benefits you think you might be deriving from such a seemingly useful library are just a figment of your diseased imagination. He informs me that he’s okay with that bit, but the half-bakedness of the implementation bothers him more. See below.
  • It doesn’t yet have a proper blame system. See abovebelow re: professor.
  • It handles inheritance only trivially: a subclass will inherit the contracts of its superclass but will not prevent you from overriding them. This means that it allows you to write contracts that fail the Liskov substitution principle. I hope to fix this soon, but in the meantime, don’t let it keep you awake at night.
  • There’s a lot of metaclass magic going on under the covers, and you shouldn’t use it in contexts with a lot of other metaclass magic (myrailsapp/lib is probably fine; myrailsapp/app is not.)
  • There’s no rdoc yet (although the code itself is fairly well-documented). Fixed. See handshake.rubyforge.org.
  • You can’t define contracts in modules.
  • And it will probably impact performance, although I haven’t conducted any performance testing.

Update: The inimitable Matthias Felleisen responds via email:

1. I never insist on a formal calculus. That’s what researchers are for. It’s a tool for exploring the ideas, communicating them in concise form, and proving basic fundamental theorems about such frameworks.

2. I do think that half-baked or quarter-baked implementations of good ideas are bad for the construction of quality software in the long term. They just propagate the silly idea that programming is shallow and anyone can do it, trained or not. One day we’ll have the equivalent of bridges come down on us in software and then the regulators will be worse than anyone can imagine now.

3. I also believe that without a properly design blame system, a contract framework provides only a 1/3 of the value that it can. That’s sad. It’s like buying a model T when we know how to build Porsches.

In my defense, this is at least two-thirds more-baked than any other Ruby contract system (5/9/07: except flgr’s ruby-contract. But I still like mine better). Since no Ruby contract system that I’ve seen is more than 15% baked, I guess that would make this… yes, about a quarter baked. It’s also a lot of fun to work on.

Finally

Find the project online at rubyforge.org/projects/handshake (MIT license), the RDoc at handshake.rubyforge.org, and the gem handshake, and please do let me know if you end up using it, and how.

no comments |

Meet RubySearch!

Posted by Brian Guthrie Wed, 14 Mar 2007 05:50:00 GMT

Update: Apparently there’s a bug in which the scrollbar doesn’t work properly the first time you run a query. You can resolve the issue by either resizing the widget or running a second query. Thanks to Kent for pointing this out; I’ll post a fix as soon as I have one. Widgets are weird.

I’ve finally gotten around to polishing RubySearch and releasing it into the wild. RubySearch is a MacOS (10.4) Dashboard widget for Ruby developers. It allows you to query local Ruby documentation (generated and stored locally by rdoc/ri) and pulls it all into a handy dandy dashboard widget, complete with hyperlinks and what have you. Which is only useful if you’re into that sort of thing, and baby, I am.

I am arbitrarily calling the first version of RubySearch version 0.6. It requires that you have Ruby installed correctly, along with its accessory ri command. Here’s the official site, here’s a direct download link, and here’s a screenshot:

Ain’t it purdy?

4 comments |

Sick of Courier New?

Posted by Brian Guthrie Wed, 14 Mar 2007 04:29:00 GMT

I was working with the TA for our Software Development class the other day and I noticed that he’d remapped his browser to display fixed-width fonts as Monaco instead of Courier New. It’s a good idea and I thought I’d repost it here in the service of others who were, like me, too dumb to come up with it on their own but still unsatisfied with their fixed-width font experience. The setting is in Firefox under Preferences -> Content -> Fonts & Colors -> Advanced.

Running Windows? This is similarly old news, but the new ClearType-enabled fonts that Microsoft released for Windows Vista work just fine in XP. I discovered this a couple of months ago when I installed Windows on my shiny new Mac via BootCamp and was suddenly acutely aware, in a way I never had been before, how ugly everything looked. Their new fixed-width font is called Consolas, and it’s lovely. You can grab them all here.

It’s not that Courier New is such a terrible font. Well actually, I guess it is.

3 comments |

Waitin' for the spam to come rolling in

Posted by Brian Guthrie Tue, 13 Mar 2007 01:52:00 GMT

Aaron says, in the comments:

You’d better put a captcha or something on here soon. I predict that you can survive about 15 days without it before each and every post is overrun with spam comments.

Call it an experiment. I started this blog last Thursday. If by next Friday I don’t have bot spam then… well, I guess I’ll continue to be lazy. If I start to see some then I’ll know that the world has discovered my existence and put up a CAPTCHA.

I’m using Typo as my blog software, which is written in Ruby on Rails, which I know well, so theoretically I could go in and hack together my own solution. I wrote a math CAPTCHA some time ago as an experiment; maybe it’s time to dust it off.

2 comments |

Three hundred morons

Posted by Brian Guthrie Mon, 12 Mar 2007 05:18:00 GMT

I saw The 300 today, which I link to not because I expect you to visit the site but because this is apparently the sort of thing one does in a blog. I didn’t care for it. It’s possible that I’m more forgiving of historical reinterpretations when they come in more flexible comic book or animation forms; this one felt actively dishonest as a movie. It also might have been the utterly one-dimensionality of the characters that threw me, although the movie certainly was beautiful.

Historical reinterpretation is most frustrating to me when the real history is just as, if not more, interesting. I know that sounds a little pretentious, and I’m cool with that.

8 comments |

Scheree Schrager has a kitty!

Posted by Brian Guthrie Mon, 12 Mar 2007 05:13:00 GMT

I am reservedly pro-Scheree but unconditionally pro-kitty. Link

2 comments |

Aaron Leans On PISA

Posted by Brian Guthrie Mon, 12 Mar 2007 04:49:00 GMT

Air reports that work continues apace on PISA, a web application designed to ease short-term financial planning and money management for struggling, irresponsible twentysomethings like ourselves. For a couple of months we were working furiously on it but I found I had to drop the commitment due to insane amounts of schoolwork.

I still really like the idea of the project, but I was surprised by how difficult it is to make financial calculations that are both helpful and reasonably accurate. Our thesis was essentially that there should be a metric, The Number, which accurately reflects the amount of “spending money” in one’s pocket on any given day. A brief quote from our project wiki:

The Number

The amount of money you have available in your pocket, on any given day, to spend on whatever you like. Calculated after bills and budget have been factored in.

Our second thesis is that when you are building an application geared towards irresponsible twentysomethings you cannot count on conscientious and reliable data input.

It turns out that you have to resolve all sorts of very difficult things before this becomes feasible. You need an accurate budget, calibrated reliably from week to week. You need a reasonably good description of the transactions that have occured in your day-to-day bank account. You need to figure out credit card charges and debts. And so forth.

We think we have a reasonable solution and it looks like Air’s churning away on it. I look forward to seeing what he comes up with and I’ll let you know if he releases a public prototype.

no comments |

Funditry with Puppies

Posted by Brian Guthrie Sat, 10 Mar 2007 21:01:00 GMT

This post is for Carolyn, who apparently prefers puppies to programming languages. It would take a colder heart than mine to disagree, although I’m more of a kitten man myself.

Brando the Black Labrador

Photo credit: Brando the Black Labrador, from The Daily Puppy.

4 comments |

Funditry with Punditry

Posted by Brian Guthrie Sat, 10 Mar 2007 20:31:00 GMT

The Economist’s economics blog, Free Exchange, explains why punditry veers into the bizarre. Read the rest of the post for the justification, but here’s the key bit:

If you want to be on television, you need to be the chap saying something that is

  1. Unequivocal
  2. Something no one else is saying
  3. Testable

There’s no money in sensible, predictable advice. Anyone can give you THAT. People latch on to sensationally successful, novel predictions and tend to ignore or write off the failures. To become a pundit, predict something remarkable that no one else is predicting and be right about it once. Then make sure everyone hears about it.

That having been said, it is possible to lose your credibility eventually. Just look at John C Dvorak.

no comments |

Shocker: FBI abused PATRIOT act

Posted by Brian Guthrie Sat, 10 Mar 2007 20:21:00 GMT

Gonzales, Mueller Admit FBI Broke Law

Transparency and accountability will never stop being a bad idea. It’s good news that their internal audit process apparently functions correctly. But their stunning arrogance at every juncture prior to the current one is dangerous and disheartening.

Secrecy is dangerous. It’s not enough to trust the current administration (not that anyone does anymore) or its chosen administrators. If you accept the precedents they set of secrecy and of the newly-expanded powers of the executive branch they you must also be prepared to trust anyone who might ever follow them, Democrat or Republican. And I’m not.

Older posts: 1 ... 3 4 5 6