Handshake vs. ruby-contract

Florian 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">=&amp;</span><span class="n">gt</span><span class="p">;</span> <span class="n">anything</span>

# accepts a block, returns an array
contract :each, Block =&gt; 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) ) ) =&gt; 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 =&gt; true, :result =&gt; 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.