Accessor_writer methods and instance_eval

Hi

I stumbled upon a problem with 'instance_eval' (using ruby 1.8.5 on
linux-x86_64). Simplified code example:

  irb:01> S = Struct.new(:a)
  => S
  irb:02> s = S.new(1)
  => #<struct S a=1>
  irb:03> s.a
  => 1
  irb:04> s.a = 2
  => 2
  irb:05> s
  => #<struct S a=2>
  irb:06> s.instance_eval { puts a }
  2
  => nil
  irb:07> s.instance_eval { a = 3 }
  => 3
  irb:08> s
  => #<struct S a=2>
  irb:09> s.instance_eval { self.a = 3 }
  => 3
  irb:10> s
  => #<struct S a=3>

My problem is in line 07: it seems that the 'a' in the block is treated as a
local variable, therefore all the writer-accessor methods ('a=') are hidden
within the block. I get the same result when instance_eval-ing the
string "a = 3" instead of the block variant.

Is there a way to avoid this behaviour without using 'self.a' or passing the
struct as a parameter into the block (which quite obsoletes the meaning of
instance_eval)?

My real world use of this pattern is using a block with 'initialize()':

  class C
    def initialize(&blk)
      @format = Struct.new( ... many, many elements with default values ...)
      @format.instance_eval(&blk)
    end
  end

To explicitly override some default format-values I want to use

  c = C.new { elem8 = 8; elem17 = 'test'; ... }

This won't work as explained above. I know I can use

  c = C.new {|format| format.elem8 = 8; format.elem17 = 'test'; ... }

or

  c = C.new { self.elem8 = 8; self.elem17 = 'test'; ... }

but both seem somewhat redundant and need too much typing (yeah I know I'm
lazy ;-).

Thanks for any suggestions.

-Thomas

···

--
<sig. under construction>

Hi~

Hi

I stumbled upon a problem with 'instance_eval' (using ruby 1.8.5 on
linux-x86_64). Simplified code example:

  irb:01> S = Struct.new(:a)
  => S
  irb:02> s = S.new(1)
  => #<struct S a=1>
  irb:03> s.a
  => 1
  irb:04> s.a = 2
  => 2
  irb:05> s
  => #<struct S a=2>
  irb:06> s.instance_eval { puts a }
  2
  => nil
  irb:07> s.instance_eval { a = 3 }
  => 3
  irb:08> s
  => #<struct S a=2>
  irb:09> s.instance_eval { self.a = 3 }
  => 3
  irb:10> s
  => #<struct S a=3>

My problem is in line 07: it seems that the 'a' in the block is treated as a
local variable, therefore all the writer-accessor methods ('a=') are hidden
within the block. I get the same result when instance_eval-ing the
string "a = 3" instead of the block variant.

Is there a way to avoid this behaviour without using 'self.a' or passing the
struct as a parameter into the block (which quite obsoletes the meaning of
instance_eval)?

My real world use of this pattern is using a block with 'initialize()':

  class C
    def initialize(&blk)
      @format = Struct.new( ... many, many elements with default values ...)
      @format.instance_eval(&blk)
    end
  end

To explicitly override some default format-values I want to use

  c = C.new { elem8 = 8; elem17 = 'test'; ... }

This won't work as explained above. I know I can use

  c = C.new {|format| format.elem8 = 8; format.elem17 = 'test'; ... }

or

  c = C.new { self.elem8 = 8; self.elem17 = 'test'; ... }

but both seem somewhat redundant and need too much typing (yeah I know I'm
lazy ;-).

Thanks for any suggestions.

-Thomas

--
<sig. under construction>

  No unfortunately you are stuck with the behavior you observed. Whenever ruby sees 'a = b' it assumes it is a local variable assignment. This happens at parse time so there is no way to affect it at runtime. It's a tradeoff in order to allow local variables to look like method calls and visca versca. So you have to prefix an assignment with self. or another object, otherwise it is a local variable assignment and you cannot work around it since it happens at parse time.

Cheers-
-- Ezra Zygmuntowicz-- Founder & Ruby Hacker
-- ez@engineyard.com
-- Engine Yard, Serious Rails Hosting
-- (866) 518-YARD (9273)

···

On Aug 23, 2007, at 4:40 PM, Thomas Gantner wrote:

Hi,

At Fri, 24 Aug 2007 08:40:08 +0900,
Thomas Gantner wrote in [ruby-talk:266021]:

Is there a way to avoid this behaviour without using 'self.a' or passing the
struct as a parameter into the block (which quite obsoletes the meaning of
instance_eval)?

Nothing. It's one of reasons why using instance_eval for such
purpose is not a good idea.

···

--
Nobu Nakada

Thanks for your quick answer (also to Ezra).

I found a solution by defining setter-methods for every struct member:

  class C
    def initialize(&blk)
      @format = Struct.new( ... many, many elements ...) do
        members.each do |key|
          self.send(:define_method, "set_#{key}".to_sym) do |new_value|
            self[key] = new_value
          end
        end
      end
      @format.instance_eval(&blk) if block_given?
    end
  end

Then I can use:

  c = C.new { set_elem8(8); set_elem17('test'); ... }

Quite readable, but not many keystrokes saved. Is there a better
alternative? I understand you suggest something without instance_eval()?

-Thomas

···

on Fri 24. August 2007 03.30, Nobuyoshi Nakada wrote:

Hi,

At Fri, 24 Aug 2007 08:40:08 +0900,
Thomas Gantner wrote in [ruby-talk:266021]:

Is there a way to avoid this behaviour without using 'self.a' or passing
the struct as a parameter into the block (which quite obsoletes the
meaning of instance_eval)?

Nothing. It's one of reasons why using instance_eval for such
purpose is not a good idea.

--
<sig. under construction>