15

How is Proc#== evaluated? RDoc says:

prc == other_proc → true or false

Returns true if prc is the same object as other_proc, or if they are both procs with the same body.

But it is not clear what counts as having "the same body". One condition seems to be that the arity must be the same:

->{} == ->{} # => true
->{} == ->x{} # => false
->x{} == ->x{} # => true
->x{} == ->y{} # => true
->x{} == ->y,z{} # => false

But there is more than that. As RDoc says, the body matters:

->{nil} == ->{nil} # => true
->{nil} == ->{false} # => false
->{false} == ->{false} # => true

But at the same time, it looks like the proc is not fully evaluated:

->{} == ->{nil} # => false
->{false} == ->{1 == 2} # => false

To what extent is the body evaluated?

Community
  • 1
  • 1
sawa
  • 165,429
  • 45
  • 277
  • 381
  • 1
    I imagine the code in the body must be the same, not the result of its evaluation. – Jim Stewart Jan 23 '13 at 19:20
  • 1
    I was thinking the parsed source tree must be identical, but... `->{nil} == ->{nil; nil} #=> true` Or perhaps the first nil is stripped form the source tree since it has no effect or meaning? – Alex Wayne Jan 23 '13 at 19:22
  • 1
    Also: `a,b = ->{}, ->{}; a == b #=> true`, but `a = ->{} [newline] b = ->{}; a == b #=> false`. Note the newline MUST be a newline; if you use a semicolon, `a` is equal to `b`. WTF indeed. – Zach Kemp Jan 23 '13 at 19:25
  • 1
    I'm not sure how it works, but it looks like in every case the Proc would do something different, the equivalence is false. Another odd example: https://gist.github.com/4611935 – Alex Wayne Jan 23 '13 at 19:30

2 Answers2

9

This has changed in Ruby 2.0, so you should not try to compare Procs. They won't be == unless they are exactly the same object.

The discussion can be found here.

If you really need to compare the code of two blocks and are using MRI, you can play around with RubyVM::InstructionSequence.disassemble(block), or even better in Ruby 2.0 RubyVM::InstructionSequence.of(block).

Marc-André Lafortune
  • 78,216
  • 16
  • 166
  • 166
  • 1
    Comparing the disassembly of the blocks is an interesting technique. It has limitations, obviously -- the procs have to share a line number (you can fake this with eval if necessary), and the values of variables in the proc's binding aren't taken into account. But I was able to create a method cache that took into account the block argument. Great tip. – rcrogers Jan 24 '13 at 17:08
  • 1
    Also, Proc#to_source is supposedly going to get implemented sometime: http://bugs.ruby-lang.org/issues/2080 – rcrogers Jan 25 '13 at 03:14
4

To answer that question lets look at the proc comparison code

static VALUE
proc_eq(VALUE self, VALUE other)
{
    if (self == other) {
        return Qtrue;
    }
    else {
        if (rb_obj_is_proc(other)) {
           rb_proc_t *p1, *p2;
           GetProcPtr(self, p1);
           GetProcPtr(other, p2);
           if (p1->envval == p2->envval &&
              p1->block.iseq->iseq_size == p2->block.iseq->iseq_size &&
              p1->block.iseq->local_size == p2->block.iseq->local_size &&
              MEMCMP(p1->block.iseq->iseq, p2->block.iseq->iseq, VALUE,
                    p1->block.iseq->iseq_size) == 0) {
                 return Qtrue;
           }
       }
    }
    return Qfalse;
}

The first if branch is quite simple - compare it two procs are the same object. The second is little bit more tricky. It checks that both of the procs have same envval, size of the iseq(proc implementation), local variables size and compares that the both implementations are identical. That means that proc equality is checked on a syntax level, not on proc result.

Lets take https://gist.github.com/4611935 First sample works just fine because number of local variables are the same and the sequence of operations are the same. Assign 123 to local variable. The second sample treated as not same because operation sequence differs - you assign 123 to different variables.

But yes, proc comparison is pretty confusing and has been removed form ruby 2.0 I presume. Now procs are compared as a regular object by its id.

Sigurd
  • 7,865
  • 3
  • 24
  • 34