Handshake vs. ruby-contract
2007-03-20 03:35:00 -0400Florian Groß (cool!) asks:
Interesting. Any chance of getting a comparison to ruby-contract? :)
ruby-contract is quite nice, and if I’d realized just how nice beforehand I probably wouldn’t have bothered with Handshake; imagine my chagrin. Its wiki is defunct, and I couldn’t gem install ruby-contract
, so I figured the project was dead or unmaintained and never took a look. Lucky for me, there are enough big differences between the two in interface and approach that it’s worth spending some time to examine them. I can’t claim to understand everything that’s going on it it yet, so Florian, please correct me if I get it wrong.
Update: I misread one of Florian’s signature examples. Fixed.
Big Picture
See my previous post for the big picture on Handshake. The idea is that you define contracts directly in classes, whose behaviors are applied to subclasses. In contrast, ruby-contract’s contracts reside in their own class and are applied to other classes through the fulfills
method. The code below comes from ruby-contract’s test cases (tc_contract.rb
) and gives a good feel for how you would interact with the code to define contracts.
class EnumerableContract < Contract
provides :each
end
module List
module NonEmpty; end
end
class ListContract < Contract
implies List
def self.check_count() 3 end
provides :each
provides :[]
provides :size do
assert(@object.size >= 0)
end
end
class NonEmptyListContract < ListContract
implies List::NonEmpty
provides :size do
assert(@object.size > 0)
end
end
class EmptyList
def each() end
def [](idx) end
def size() 0 end
fulfills ListContract
end
class NonEmptyList < EmptyList
def size() 1 end
fulfills NonEmptyListContract
end
This code does a fair number of things that Handshake doesn’t attempt. Because Handshake agreements are defined either directly within a class or within its superclass there’s no way or reason to verify that a particular class provides or doesn’t provide a method. I also really, really like the syntax for the provides
precondition checks. On the other hand, ruby-contract doesn’t appear to allow you to check class invariants, or to check postconditions in the context of all of a method’s arguments. Florian, please correct me if I’m mistaken.
Method Signature Checking
Both libraries offer method signature checking. I happen to prefer my syntax, but it’s mostly an aesthetics thing. I’m dismayed to discover that my idea of using the === operator for these clauses isn’t half as clever as I thought it was, or perhaps we are simply both clever. :)
Handshake:
# accepts a string, return value doesn't matter
contract :name=, String => anything<span class="c1"># accepts a string, return value doesn't matter</span>
<span class="n">contract</span> <span class="ss">:name</span><span class="o">=</span><span class="p">,</span> <span class="no">String</span> <span class="o">=&</span><span class="n">gt</span><span class="p">;</span> <span class="n">anything</span>
# accepts a block, returns an array
contract :each, Block => Array
# Matches something that is an Enumerable and that responds to
# either :to_ary or :to_a.
contract :x, all?(
Enumerable,
any?(
responds_to?(:to_a),
responds_to?(:to_ary) ) ) => anything
ruby-contract:
# accepts a string, return value doesn't matter
signature(:name=, String)<span class="c1"># accepts a string, return value doesn't matter</span>
<span class="n">signature</span><span class="p">(</span><span class="ss">:name</span><span class="o">=</span><span class="p">,</span> <span class="no">String</span><span class="p">)</span>
# accepts a block, returns an array
signature(:each, :block => true, :result => Array)
# Matches something that is an Enumerable and that responds to
# either :to_ary or :to_a.
# From ruby-contract docs.
signature :x, Contract::Check::All[
Enumerable,
Contract::Check::Any[
Contract::Check::Quack[:to_a],
Contract::Check::Quack[:to_ary]
]
]
We both provided remarkably similar combinators for signature clauses, although mine are defined methods and his are classes. Mine are quite expressive in terms of the error messages they generate, but I haven’t checked ruby-contract in that regard.
Implementation
Handshake is implemented as a proxy object that resides on top of an object and intercepts all method calls, running them through invariant, signature, and general pre- and post-condition checks. It uses this to ascertain blame as necessary, although the exception messages are generated by the checks themselves. The proxy object looks, acts, and feels like the real object.
In contrast, if I understand things correctly, ruby-contract uses eval to redefine methods that the programmer wants to check. For end users, the difference lies in the way calls to methods within the same class are evaluated. In Handshake, the proxy object establishes a barrier but cannot monitor anything that occurs inside that barrier. This means that calls to private methods are effectively uncheckable. The upside is that it allows you to define invariants in terms of methods as well as instance variables without getting stuck in an infinite loop. This is particularly useful for cases where you want to extend the behavior of a core Ruby class but don’t have access to its internals; see the NonEmptyArray example from my last post.
ruby-contract also hooks into Test::Unit to allow access to its assert methods within provides
blocks and utilizes its inheritance properties to automatically check those clauses as soon as a class is defined as fulfilling a contract. This is an insanely good idea and I may stealborrow some portion of it.
Conclusion
Both libraries provide some unique and complementary contract mechanisms. ruby-contract is more mature, judging by the code, although it hasn’t been updated for a while. Not to be all wishy-washy, but it looks like there are projects that could benefit from either library, or both. I’m glad I finally took the time to dig through it.