hoodwink.d enhanced
RSS
2.0
XHTML
1.0

RedHanded

Digging Deep with Instance_eval #

by why in inspect

I feel like instance_eval deserves a friendlier name. Especially when used in tandem with a block to reach down inside an object for a moment.

A great example of this is on the RubyGarden wiki under RubyStyleGuide/InjectComplexTestsIntoObjects. The challenge is to reduce the redundancy of the below.

 f = open("myfile")
 if f.stat.readable? and f.stat.writable? and f.stat.size? and f.stat.owned?
   # work with file
 end

My favorite solution involves instance_eval, but see how long and ugly it looks? Perhaps an answer would be aliasing a name like within for simple block calls to instance_eval.

 f = open("myfile")    
 if f.stat.instance_eval{ readable? and writable? and size? and owned? }
   # work with file
 end
said on 01 Jun 2005 at 16:56

Used that way, it’s a bit like a with block in Visual Basic, actually.

(Horrid comparison, I know, but it’s the first thing that came to mind. And it is a convenient thing sometimes.)

said on 01 Jun 2005 at 17:03

Ah, I see someone’s actually gone and done an Object#with, though employing method_missing? to do it.

The disadvantage of instance_eval over the method_missing? is that you can’t easily call private methods from the enclosing scope.

Doesn’t matter for what you’re trying to do here, but it’s limiting in more general usage.

said on 01 Jun 2005 at 17:16

Another solution for minimizing redundancy in this particular case might be:


f = open("myfile")
if %w{readable? writable? size? owned?}.inject(true) {|o,p| o and f.stat.send p}
  # work with file
end

Heh. Horrid, no?

said on 01 Jun 2005 at 17:27

I think MenTaLguY needs a spanking for that last one.

said on 01 Jun 2005 at 18:33

How about

f = open("myfile")
if %w{readable? writable? size? owned?}.all? { |m| f.stat.send(m) }
  # work with file
end
said on 01 Jun 2005 at 21:56

f.stat is called for each invocation of the block for MenTaLguY and timsuth’s examples, which could be inefficient. So a separate variable is needed for best efficiency. Or instance_eval could be used, but that gets real ugly:


f = open("myfile")
if f.stat.instance_eval { %w{readable? writable? size? owned?}.all? { |m| send(m) } }
  # work with file
end

By the way, here is another version:


f = open("myfile")
if f.stat.instance_eval { eval(%w{readable? writable? size? owned?}.join(' and ')) }
  # work with file
end

Not all that clever, but just obfuscated enough to be interesting ;)

Hey _why, what is wrong with the “is?” method created on the above linked page? That sure looks clean to me…

said on 01 Jun 2005 at 23:09

What would be nice would be a method to do a series of sends to an object and collect the results. (“Collect” would actually be a great name for this, shame it’s a synonym for “map” instead).


f = open("myfile")
if f.stat.collect [:readable?, :writable?, :size?, :owned?].all?
   # work with file
end

said on 02 Jun 2005 at 00:13

MrCode: nothing wrong with it. How would you do “or” logic with it, if you wanted? I guess multiple “is?” calls.

said on 02 Jun 2005 at 03:22

I think Ruby has other ways to mimic a with-like behaviour.

My favorite: Extending classes.

class File::Stat
    def usable?
 readable? and writable? and size? and owned?
    end
end

if open('myFile').stat.usable?
    # use it
end

It seems logical that the File::Stat object is responsible for managing that.

said on 02 Jun 2005 at 12:51

no pls, not yet another reinvention of


 def with(x,&b);x.instance_eval(&b) end

I find the one based on method_missing ugly. Not thread-safe, too much code…

said on 02 Jun 2005 at 16:59

You could make a threadsafe one actually.


class DoubleDelegate
  def initialize(a, b)
    @a = a
    @b = b
  end
  def method_missing(method, *args, &block)
    if @a.respond_to?(method, true)
 @a
    else
      @b
    end.send(method, *args, &block)
  end
end

module Kernel
  def with(o, &block)
    DoubleDelegate::new(o, self).instance_eval &block
  end
  private :with
end

Wouldn’t have immediate access to the object’s instance variables, but if you did you’d be using instance_eval anyway.

Private methods will be dispatched to the object passed as a parameter to with, if it responds to them, else they will be dispatched in the calling context.

Of course this won’t work totally. Object/Kernel methods on DoubleDelegate will take the highest precedence of all.

You can have DoubleDelegate undefine any methods it gets from its ancestors, or you can find some way to make DoubleDelegate a class that doesn’t inherit from Object or Kernel.

said on 02 Jun 2005 at 17:13

I imagine this sort of thing is why Matz wants a “bare” superclass for Object, actually.

said on 02 Jun 2005 at 17:26

Until then I suppose you can do something like this, if you have Evil Ruby or similar installed to get Object#class=


Yorick = Class::allocate
class Yorick
  def self.allocate
    o = Object::new
    o.class = Yorick
    o
  end
  def self.new(*args, block)
    o = allocate
 o.send(:initialize, *args, &block) \
      if o.respond_to?(:initialize, true)
    o
  end
end

class DoubleDelegate < Yorick
  ... etc
end

Untested. If you try it and open a portal to Hell or something, you were warned.

Comments are closed for this entry.