Search code examples
rubymetaprogrammingintrospection

Compare the Content, Not the Results, of Procs


Using Ruby 1.9.2

Problem
Compare the content, not the results, of two procs. I understand the results can't be tested because of the halting problem but that's OK; I don't want to test the results anyway.

For instance

proc {@x == "x"} == proc {@x == "x"}  => false # doh!

That returns false because the objects inside the procs are not the same.

My clunky solution
I have a work around solution that kinda sorta does what I want but it doesn't really test that the proc is "equal" to what I put in it. In my specific case the format of my procs will always be boolean tests on instance variables like this:

{@x == "x" && @y != "y" || @z == String} 

I wrote a method that builds classes dynamically and creates instance variables set to specified values:

def create_proc_tester(property_value_hash)
  new_class = Class.new.new

  new_class.class.class_eval do
     define_method(:xql?) { |&block| instance_eval &block }
  end

  property_value_hash.each do |key, value| 
    new_class.instance_variable_set("@#{key}", value)
  end

  new_class
end

Which could be used something like this:

class Foo
  attr_accessor :block
end

foo = Foo.new
foo.block = proc {@x == "x" && @y != "y" || @z == String}

tester = create_proc_tester(:x => "x", :y => "y", :z => Fixnum)
puts "Test #1: #{tester.xql? &foo.block}"
tester = create_proc_tester(:x => "x", :y => "x", :z => String)
puts "Test #2: #{tester.xql? &foo.block}"

> Test #1: false
> Test #2: true

.
.
That's all great and wonderful but I want to know if there is a better, more meta, way to do this that actually tests the contents of the proc not just a work around that solves my specific problem; something that could be used to test any proc.

I was thinking there might be a way to use the Ruby parser to get something to compare but I have no idea how. I'm researching it now but thought I'd try to see if anyone here has done this before and knows how. That might be a dead-end though because of the dynamic nature of Ruby but that's where I'm looking now.


Solution

  • If you're using Ruby 1.9, you may be able to use the sourcify gem.

    $ irb
    > require 'sourcify'
    => true 
    > a = proc {@x == "x"}
    => #<Proc:0x9ba4240@(irb):2> 
    > b = proc {@x == %{x}}
    => #<Proc:0x9ba23f0@(irb):3> 
    > a == b
    => false 
    > a.to_source == b.to_source
    => true 
    > RUBY_VERSION
    => "1.9.2" 
    

    We also ran into the ParseTree/Ruby 1.9 incompatibility problem at my company.