The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Frost-safe DSL'ing with instance_exec

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Eigen Class

Posts: 358
Nickname: eigenclass
Registered: Oct, 2005

Eigenclass is a hardcore Ruby blog.
Frost-safe DSL'ing with instance_exec Posted: Feb 13, 2006 8:52 AM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by Eigen Class.
Original Post: Frost-safe DSL'ing with instance_exec
Feed Title: Eigenclass
Feed URL: http://feeds.feedburner.com/eigenclass
Feed Description: Ruby stuff --- trying to stay away from triviality.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Eigen Class
Latest Posts From Eigenclass

Advertisement

Object#instance_eval is often of use when creating a DSL with Ruby, but it's not as powerful as the Object#instance_exec method introduced in the 1.9 branch, which can be used as in

o = Struct.new(:val).new(1)
o.instance_exec(1){|arg| val + arg }               # => 2

i.e. it allows you to pass arguments to the block which is to be evaluated with a new self*1.

This is one of the 1.9 features I wouldn't mind having in 1.8.

My first implementation, posted to ruby-talk, was

class Object
  def instance_exec(*args, &block)
    mname = "__instance_exec_#{Thread.current.object_id.abs}"
    class << self; self end.class_eval{ define_method(mname, &block) }
    begin
      ret = send(mname, *args)
    ensure
      class << self; self end.class_eval{ undef_method(mname) } rescue nil
    end
    ret
  end
end
    

It operates by defining a temporary method in the singleton class of the object to be used as the new self inside the block, easily passing the basic test provided by Jim Weirich in ruby-talk:179038:

class Dummy
  def f
    :dummy_value
  end
end

require 'test/unit'
class TestInstanceEvalWithArgs < Test::Unit::TestCase
  def test_instance_exec
    # Create a block that returns the value of an argument and a value
    # of a method call to +self+.  
    block = lambda { |a| [a, f] }

    assert_equal [:arg_value, :dummy_value], 
      Dummy.new.instance_exec(:arg_value, &block)
  end
end

#>> Loaded suite -
#>> Started
#>> .
#>> Finished in 0.000566 seconds.
#>> 
#>> 1 tests, 1 assertions, 0 failures, 0 errors

That instance_exec implementation is thread-safe thanks to the Thread.current.object_id trick, but it doesn't work with immediate values (Fixnums and friends), and most importantly it bombs when given a frozen object:

class TestInstanceEvalWithArgs 
  def test_instance_exec_with_frozen_obj
    block = lambda { |a| [a, f] }

    obj = Dummy.new
    obj.freeze
    assert_equal [:arg_value, :dummy_value],
      obj.instance_exec(:arg_value, &block)
  end
end
#>> Loaded suite -
#>> Started
#>> .E
#>> Finished in 0.000831 seconds.
#>> 
#>>   1) Error:
#>> test_instance_exec_with_frozen_obj(TestInstanceEvalWithArgs):
#>> TypeError: can't modify frozen class/module
#>>     -:9:in `define_method'
#>>     -:9:in `instance_exec'
#>>     -:9:in `instance_exec'
#>>     -:59:in `test_instance_exec_with_frozen_obj'
#>> 
#>> 2 tests, 1 assertions, 0 failures, 1 errors

At that point I remembered there had been some discussion on ruby-core regarding instance_exec. A quick google search showed that there's a number of implementations floating around. Funnily enough, my implementation was very similar to Ara Howard's, posted in the original ruby core thread*2.

I also discovered that Rails ships with its own instance_exec in active_support:

class Proc 
  def bind(object)
    block, time = self, Time.now
    (class << object; self end).class_eval do
      method_name = "__bind_#{time.to_i}_#{time.usec}"
      define_method(method_name, &block)
      method = instance_method(method_name)
      remove_method(method_name)
      method
    end.bind(object)
  end
end

class Object
  def instance_exec(*arguments, &block)
    block.bind(self)[*arguments]
  end
end

That definition is essentially equivalent to the other ones, but not strictly thread-safe, though (it will fail if your box or your Ruby interpreter are really fast ;), and when you decide to mess up with the system clock).

I also found Facets mentioned in that context, but was too lazy to find the instance_exec equivalent amongst its 400+ methods(!) (or maybe it was never added actually?).

Implementing a frost-safe #instance_exec


Read more...

Read: Frost-safe DSL'ing with instance_exec

Topic: Rails Nits — Error Messages Previous Topic   Next Topic Topic: Subversion en un hosting &#8220;compartido&#8221;

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use