Posts tagged ‘MRI’

December 19, 2008

Dupping Our Way Out of Instance Eval?

Although Instance_eval can be a great tool for creating clean DSLs it does, unfortunately, suffer from a few shortcomings. These shortcomings are caused by the ‘change in self’  of the block and may, in some circumstances,  lead to surprising behaviour.

_why detailed a lot of these shortcomings in his blog post and proposed a wonderful solution: Don’t Change Self.

_why’s idea was to add the functionality that the instance_eval was to provide directly to the binding of the block itself. Using this technique we get the best of both worlds: we get the clean interface for our DSL and we eliminate the surprising and annoying behaviour of instance_eval in relation to local instance vars and methods.

How did _why do it?

_why first created a little C extension called ‘Mixico’ which makes it possible to ‘unmix’ modules from inheritance chains.His replacement for instance_eval, called mix_eval, works as follows:

  1. Get the binding of the block.
  2. Mix the Module containing the functionality we need into the binding.
  3. Yield to the block
  4. Unmix the Module from the binding (Using Mixico).

The above does work well and is a great and original idea but there are still a few problems with it.

The biggest problem (in my opinion) is that you must encapsulate the functionality you need in a Module; this results in the following syntax when invoking mix_eval:

mix_eval(my_module) { .../* the block */ }

#compare this to instance_eval
instance_eval {... /* the block */ }

This limitation is due to the fact that Mixico only works with Modules; it is not really possible (notwithstanding the recent rb_mod_mixin_object patch) to mix in Objects or Classes into the block binding.

Another problem is that mix_eval is not thread-safe.

A thread-safe mix_eval

One solution to the thread-safety issue was discovered by coderrr (http://coderrr.wordpress.com/2008/11/14/making-mixico-thread-safe/). His solution was to duplicate the binding of the block and perform the mix_eval on the duplicate.

However a run-of-the-mill duplicate is not a good solution if the user needs to modify the binding itself. The ‘duplicate’ was instead made to be (effectively) an IClass of the binding.

That is its m_tbl and iv_tbl pointers were set to the method table and ivar table of the original binding. coderrr’s solution worked as follows:

  1. Get the binding of the block
  2. Make an IClass from the binding
  3. Mix the Module containing the functionality we need into the IClass.
  4. Instance_Eval the block in the context of the IClass
  5. Unmix the Module from the IClass
  6. Reset the iv_tbl and m_tbl pointers of the IClass (to prevent a GC crash)

As far as I can tell coderrr’s solution is thread-safe (and very clever too IMO).

Dup_eval

Dup_eval is heavily based on coderrr’s work but with the realization that since we’re dealing with a duplicate we no longer need the machinery of Mixico.

That is, since the IClass is a duplicate of the actual binding we can simply discard it once we’ve finished and let it be GC’d (no need to unmix any Modules).

Another feature of Dup_eval is that it is not limited to mixing in Modules. It makes use of another small extension called Object2module that is capable of mixing in Objects and Classes.

As a result Dup_eval provides two methods: a straight forward dup_eval() that implicitly mixes in self and a dup_eval_with() method that lets you specify any number of Classes/Objects/Modules to mix into the binding. Note that the dup_eval() syntax is identical to instance_eval:

#dup_eval() syntax (implictly mixes self into the block binding)
my_object.dup_eval { /* ... the block... */ }

#dup_eval_with() syntax (does not implictly mix in self)
my_object.dup_eval_with(mod1, obj1, class1) { /* ...the block... */ }

Here is how Dup_eval works:

  1. Get the binding of the block
  2. Make an IClass from the binding
  3. Mix in the Object(s) (or Class(es) or Module(s)) into the IClass (Using Object2module)
  4. Instance_Eval the block in the context of the IClass
  5. Reset the iv_tbl and m_tbl pointers of the IClass (to prevent a GC crash)
  6. Discard the IClass

Summary

Dup_eval is just another attempt at expanding the power and flexibility of Ruby and is provided as-is in the hope it will aid or interest other Ruby developers.

For more information on possible applications of a program like dup_eval check out _why’s blog entry on mix_eval.

Also be sure to check out coderrr’s blog.

Downloads

Please be aware that dup_eval is a new project and possibly still has warts; use at own risk! ;)

Install the gem: sudo gem install dup_eval

git project: http://github.com/banister/dup_eval/tree/master

Follow

Get every new post delivered to your Inbox.