views:

209

answers:

1

In my application, I have the need to allow blocks to be defined and called within the scope of a class, using instance_exec (via Rails 2.3.2). However, some of these blocks need to return early in some situations, which is causing me a problem.

My application was built using ruby 1.8.6, but I need to get it running on 1.8.7 as well. It seems that between the two versions the ability to return from within a lambda was removed. The following works in 1.8.6, but throws a LocalJumpError (unexpected return) in 1.8.7:

class Foo
  def square(n)
    n ** 2
  end

  def cube(n)
    n ** 3
  end

  def call_block(*args, &block)
    instance_exec *args, &block
  end
end

block = lambda { |n|
  return square(n) if n < 5
  cube(n)
}

f = Foo.new
f.call_block(5, &block) # returns 125
f.call_block(3, &block) # returns 9 in 1.8.6, throws a LocalJumpError in 1.8.7

I determined that I could get it working in 1.8.7 if I replaced return in my block with next, but next square(n) if n < 5 results in nil in 1.8.6.

Is there any way I can get this working in both 1.8.6 and 1.8.7? I know that I can restructure my blocks to use branching instead of an early return, but some blocks are more complex and have multiple situations where an early return is needed.

Also, is this going to change further if I want to get my code running in ruby 1.9?

Edit: I've discovered that the reason it works in 1.8.6 and not 1.8.7 is that 1.8.7 defines its own instance_exec in the C source, while 1.8.6 uses Rails' implementation. If I override instance_exec in 1.8.7 with Rails' version, it works there too.

+1  A: 

Edit after comments See this post for details.

class Foo

  def square(n)
    n ** 2
  end

  def cube(n)
    n ** 3
  end

  def call_block(*args, &block)
      instance_exec *args, &block
    end
end




def a
  block = lambda { | n|
    return square(n) if n < 5
    cube(n)
  }
 f = Foo.new 
puts f.call_block(3, &block) # returns 125
puts "Never makes it here in 1.8.7"
puts f.call_block(5, &block) # returns 9 in 1.8.6, returns nothing in 1.8.7
end

a

This code results in nothing, since it returns outside of the puts statements.

The way procs and lambdas work changed in 1.9. So that helps explain whats going on.

Original

I refactored your code and it worked under all 3 vm's. Interestingly, your code did execute under 1.9 without an exception.

class Foo

  def square(n)
    n ** 2
  end

  def cube(n)
    n ** 3
  end

  def call_block(*args, &block)
    block.call(self, *args)
  end
end

block = lambda { |obj, n|
  return obj.square(n) if n < 5
  obj.cube(n)
}

f = Foo.new
puts f.call_block(5, &block) # returns 125
puts f.call_block(3, &block) # returns 9

This post might be of some insight.

coreypurcell
I voted this up because your refactoring does work; however, the point of using instance_exec was so that the object doesn't need to be passed to the blocks. This is part of a templating engine where users (of the code) are able to create their own commands, so I wanted to make it as simple as possible to do so.
Daniel Vandersluis
Ok, I understand. The users will define the block. I found it interesting that your code worked fine under ruby 1.9 but not under 1.8.7. Instance_exec only exists in 1.8.7 and 1.9. Rails added its own version in Active_support which allowed it to be used in 1.8.6.
coreypurcell
I think I understand what is going on now. See the edit.
coreypurcell