OpenStruct respond_to? problem

I blindly assumed that OpenStruct would handle respond_to? properly, but I'm seeing the following behavior:

   o = OpenStruct.new( :type_id => "string" )
   p o.type_id #-> "string"
   p o.respond_to?( :type_id ) #-> false

Is this the correct behavior for OpenStruct, or should this be reported as a bug?

- Jamis

···

--
Jamis Buck
jgb3@email.byu.edu
http://www.jamisbuck.org/jamis

"I use octal until I get to 8, and then I switch to decimal."

I am looking for a structure like:

my_obj = boxWidget.new
with my_obj do
    x = 0
    y = 10
    width = 100
    height = 100
end

instead of having to say

my_obj = boxWidget.new
my_obj.x = 0
my_obj.y = 10
my_obj.width = 100
my_obj.height = 100

Can you do this already? If not....anyone support an RCR for it besides me?

Zach

I think that OpenStruct uses #method_missing.

-austin

···

On Tue, 24 Aug 2004 09:59:15 +0900, Jamis Buck <jgb3@email.byu.edu> wrote:

I blindly assumed that OpenStruct would handle respond_to? properly, but
I'm seeing the following behavior:

   o = OpenStruct.new( :type_id => "string" )
   p o.type_id #-> "string"
   p o.respond_to?( :type_id ) #-> false

Is this the correct behavior for OpenStruct, or should this be reported
as a bug?

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

Zach Dennis wrote:

I am looking for a structure like:

my_obj = boxWidget.new
with my_obj do
   x = 0
   y = 10
   width = 100
   height = 100
end

instead of having to say

my_obj = boxWidget.new
my_obj.x = 0
my_obj.y = 10
my_obj.width = 100
my_obj.height = 100

Can you do this already? If not....anyone support an RCR for it besides me?

No such beast "out of the box," but there have been various discussions on the list about this feature. I believe one implementation presented was:

   class Object
     def with(obj, &block)
       obj.instance_eval &block
     end
   end

   with "hello" do
     puts length
     puts reverse
   end

- Jamis

···

--
Jamis Buck
jgb3@email.byu.edu
http://www.jamisbuck.org/jamis

"I use octal until I get to 8, and then I switch to decimal."

Sure. This has been talked about before. Unfortunately I can't seem to find
anything on it. Ruby-talk searches really blow (sorry, but its true).

Someone wrote the code for it before, (anyone?) but in my experience it wasn't
very useful. It prevents easy code refactoring.

···

On Monday 23 August 2004 09:08 pm, Zach Dennis wrote:

my_obj = boxWidget.new
with my_obj do
    x = 0
    y = 10
    width = 100
    height = 100
end

--
T.

Hi --

I am looking for a structure like:

my_obj = boxWidget.new
with my_obj do
    x = 0
    y = 10
    width = 100
    height = 100
end

instead of having to say

my_obj = boxWidget.new
my_obj.x = 0
my_obj.y = 10
my_obj.width = 100
my_obj.height = 100

Can you do this already? If not....anyone support an RCR for it besides me?

Every assignment to a bare identifier (x = 0) is parsed as an
assignment to a local variable. If you want to call a "="-style
method, you have to give the receiver explicitly. So I don't think a
"with" keyword would add much to what you can already do with
instance_eval:

  my_obj = BoxWidget.new
  my_obj.instance_eval do
    self.x = 0
    self.y = 0
    ...
  end

which then isn't a whole lot better than my_obj.x = 0 and so on.

Another thing people sometimes do is:

  my_obj = BoxWidget.new do |bw|
    bw.x = 0
    bw.y = 10
    ...
  end

which can be nice because it packages the initialization with the
creation of the object, but of course it only works if
BoxWidget#initialize yields the new object.

David

···

On Tue, 24 Aug 2004, Zach Dennis wrote:

--
David A. Black
dblack@wobblini.net

Indeed it does. If respond_to? is to work, you'd need to implement
it. Utterly untested:

  class OpenStruct
    def respond_to?(sym)
      super or @table.key?(sym.to_sym)
    end
  end

There's probably several problems with OpenStruct along these lines.
I don't think #hash is implemented, for example.

Gavin

···

On Tuesday, August 24, 2004, 1:03:40 PM, Austin wrote:

On Tue, 24 Aug 2004 09:59:15 +0900, Jamis Buck <jgb3@email.byu.edu> wrote:

I blindly assumed that OpenStruct would handle respond_to? properly, but
I'm seeing the following behavior:

   o = OpenStruct.new( :type_id => "string" )
   p o.type_id #-> "string"
   p o.respond_to?( :type_id ) #-> false

Is this the correct behavior for OpenStruct, or should this be reported
as a bug?

I think that OpenStruct uses #method_missing.

Austin Ziegler wrote:

···

On Tue, 24 Aug 2004 09:59:15 +0900, Jamis Buck <jgb3@email.byu.edu> wrote:

I blindly assumed that OpenStruct would handle respond_to? properly, but
I'm seeing the following behavior:

  o = OpenStruct.new( :type_id => "string" )
  p o.type_id #-> "string"
  p o.respond_to?( :type_id ) #-> false

Is this the correct behavior for OpenStruct, or should this be reported
as a bug?

I think that OpenStruct uses #method_missing.

I think you're right. However, that doesn't preclude the possibility of OpenStruct also implementing a custom "respond_to?" method... Would this be desirable, or would it have other (negative) consequences?

- Jamis

--
Jamis Buck
jgb3@email.byu.edu
http://www.jamisbuck.org/jamis

"I use octal until I get to 8, and then I switch to decimal."

my very first thought was of .class_eval, but "obviously"
.instance_eval is correct.

i find this interesting because this seems to me to be the way to do
prototype-based objects rather than class-based objects in ruby;
similar to the Self language w/r/t smalltalk. (though offhand i'm not
sure how an object would get cloned before doing this modifying.)

peace,
-z

···

On Tue, 24 Aug 2004 11:03:23 +0900, David A. Black <dblack@wobblini.net> wrote:

Hi --

On Tue, 24 Aug 2004, Zach Dennis wrote:

> I am looking for a structure like:
>
> my_obj = boxWidget.new
> with my_obj do
> x = 0
> y = 10
> width = 100
> height = 100
> end
>
> instead of having to say
>
> my_obj = boxWidget.new
> my_obj.x = 0
> my_obj.y = 10
> my_obj.width = 100
> my_obj.height = 100
>
>
>
> Can you do this already? If not....anyone support an RCR for it besides me?

Every assignment to a bare identifier (x = 0) is parsed as an
assignment to a local variable. If you want to call a "="-style
method, you have to give the receiver explicitly. So I don't think a
"with" keyword would add much to what you can already do with
instance_eval:

  my_obj = BoxWidget.new
  my_obj.instance_eval do
    self.x = 0
    self.y = 0
    ...
  end

which then isn't a whole lot better than my_obj.x = 0 and so on.

Another thing people sometimes do is:

  my_obj = BoxWidget.new do |bw|
    bw.x = 0
    bw.y = 10
    ...
  end

which can be nice because it packages the initialization with the
creation of the object, but of course it only works if
BoxWidget#initialize yields the new object.

David

--
David A. Black
dblack@wobblini.net

"Jamis Buck" <jgb3@email.byu.edu> schrieb im Newsbeitrag
news:412A9A3D.2050808@email.byu.edu...

Zach Dennis wrote:
> I am looking for a structure like:
>
> my_obj = boxWidget.new
> with my_obj do
> x = 0
> y = 10
> width = 100
> height = 100
> end
>
> instead of having to say
>
> my_obj = boxWidget.new
> my_obj.x = 0
> my_obj.y = 10
> my_obj.width = 100
> my_obj.height = 100
>
>
>
> Can you do this already? If not....anyone support an RCR for it besides

me?

No such beast "out of the box," but there have been various discussions
on the list about this feature. I believe one implementation presented

was:

   class Object
     def with(obj, &block)
       obj.instance_eval &block
     end
   end

   with "hello" do
     puts length
     puts reverse
   end

Doesn't work for member init since all statements of the form "x=y" create
local variables in the block. I'd prefer something like:

class Object
  def member_init(map)
    map.each do |name, val|
      send "#{name}=", val
    end
    self
  end
end

ost = OpenStruct.new.member_init(
  :name => "foo",
  :age => 10
)

Kind regards

    robert

Thanks everyone who replied....I like the instance_eval solution, although I think it would be nice if *with* were on a Object level, like Jamis Buck first posted...

class Object
    def with( obj , &block )
       obj.instance_eval &block
    end
end

class Test
   def initialize
       @x = 5
    end
    def x
       return x
    end
end

t = Test.new
with t do
    puts @x x = 35
    @x = 35
end

puts t.x

I think the *with* block acts as expected as far as instance variables and local variables are conerned. When you initialize an object you have to specify the '@' or 'self.' before the variable name. So it makes sense that in the *block* it would be treated the same.

I think it is so much cleaner to say:

with obj do
    ...
end

rather then:

obj.instance_eval do

end

I guess there isn't a huge difference and I might just be being picky, but I totally think the *with* block is a much cleaner solution.

Zach

Hi --

> Every assignment to a bare identifier (x = 0) is parsed as an
> assignment to a local variable. If you want to call a "="-style
> method, you have to give the receiver explicitly. So I don't think a
> "with" keyword would add much to what you can already do with
> instance_eval:
>
> my_obj = BoxWidget.new
> my_obj.instance_eval do
> self.x = 0
> self.y = 0
> ...
> end
>
> which then isn't a whole lot better than my_obj.x = 0 and so on.
>
> Another thing people sometimes do is:
>
> my_obj = BoxWidget.new do |bw|
> bw.x = 0
> bw.y = 10
> ...
> end
>
> which can be nice because it packages the initialization with the
> creation of the object, but of course it only works if
> BoxWidget#initialize yields the new object.

my very first thought was of .class_eval, but "obviously"
.instance_eval is correct.

i find this interesting because this seems to me to be the way to do
prototype-based objects rather than class-based objects in ruby;
similar to the Self language w/r/t smalltalk. (though offhand i'm not
sure how an object would get cloned before doing this modifying.)

There's not really much object modifying here, compared to what you
*can* do :slight_smile: (adding methods to individual objects, etc.) In general
I tend to think of Ruby objects as not very class-based: once an
object has been launched into objectspace by its class, it follows a
path of its own, in which the class of origin may play a role but
which is not constrained by it. (And of course Class objects
themselves can change.)

David

···

On Tue, 24 Aug 2004, zuzu wrote:

On Tue, 24 Aug 2004 11:03:23 +0900, David A. Black <dblack@wobblini.net> wrote:

--
David A. Black
dblack@wobblini.net

Also with the with block being how Jamis Buck and David A. Black posted it works nice with methods as well.

def talk
    puts "i'm talking"
end

def Test
    def test
       puts "i'm testing"
    end
end

t = Test.new
with t do
    talk
    test
end

'talk' is called correctly because it is in the current scope of the *with* block and 'test' is called correctly because it exists on object 't'.

I think this would be expected behavior as well. If someone was worried about method confusion, they could simply say "self." like David A. Black suggested in one of his original posts on this thread.

And it doesn't add complexity to how things work in ruby. It just appears to be a more elegant solution, syntactically.

Zach

Zach Dennis wrote:

···

Thanks everyone who replied....I like the instance_eval solution, although I think it would be nice if *with* were on a Object level, like Jamis Buck first posted...

class Object
   def with( obj , &block )
      obj.instance_eval &block
   end
end

class Test
  def initialize
      @x = 5
   end
   def x
      return x
   end
end

t = Test.new
with t do
   puts @x x = 35
   @x = 35
end

puts t.x

I think the *with* block acts as expected as far as instance variables and local variables are conerned. When you initialize an object you have to specify the '@' or 'self.' before the variable name. So it makes sense that in the *block* it would be treated the same.

I think it is so much cleaner to say:

with obj do
   ...
end

rather then:

obj.instance_eval do

end

I guess there isn't a huge difference and I might just be being picky, but I totally think the *with* block is a much cleaner solution.

Zach

Zach Dennis said:

Also with the with block being how Jamis Buck and David A. Black posted
it works nice with methods as well.

def talk
    puts "i'm talking"
end

def Test
    def test
       puts "i'm testing"
    end
end

t = Test.new
with t do
    talk
    test
end

'talk' is called correctly because it is in the current scope of the
*with* block and 'test' is called correctly because it exists on object
't'.

The above works, but some very similar code is broken ... For example:
  class Test
    def test
      puts "i'm testing"
    end
  end

  class Dog
    def talk
      puts "i'm talking"
    end

    def testing_dog
      talk # Class Dog#talk
      t = Test.new
      with t do
        test # Calls Test#test
        talk # ERROR: undefined local variable or method `talk'
      end
    end
  end

This is because the second call to talk is not evaluated in the lexical
scope of the calling object Dog, but within the scope of the Test object.
Dog#talk exists, but Test#talk does not. (Your example missed the problem
by putting +talk+ into the global scope.

There is an awkward work around. First write +with+ like this:

  module Kernel
    def with(obj, &block)
      obj.instance_eval { @self = eval("self", block) }
      obj.instance_eval &block
    end
  end

Then reference Dog#talk like this ...

  class Dog
    def talk
      puts "i'm talking"
    end

    def testing_dog
      talk # Class Dog#talk
      t = Test.new
      with t do
        test # Calls Test#test
        @self.talk # Calls Dog#talk
      end
    end
  end

···

--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)

Zach Dennis said:

Also with the with block being how Jamis Buck and David A. Black posted
it works nice with methods as well.

def talk
    puts "i'm talking"
end

def Test
    def test
       puts "i'm testing"
    end
end

t = Test.new
with t do
    talk
    test
end

'talk' is called correctly because it is in the current scope of the
*with* block and 'test' is called correctly because it exists on object
't'.

The above works, but some very similar code is broken ... For example:
  class Test
    def test
      puts "i'm testing"
    end
  end

  class Dog
    def talk
      puts "i'm talking"
    end

    def testing_dog
      talk # Class Dog#talk
      t = Test.new
      with t do
        test # Calls Test#test
        talk # ERROR: undefined local variable or method `talk'
      end
    end
  end

This is because the second call to talk is not evaluated in the lexical
scope of the calling object Dog, but within the scope of the Test object.
Dog#talk exists, but Test#talk does not. (Your example missed the problem
by putting +talk+ into the global scope.

There is an awkward work around. First write +with+ like this:

  module Kernel
    def with(obj, &block)
      obj.instance_eval { @self = eval("self", block) }
      obj.instance_eval &block
    end
  end

Then reference Dog#talk like this ...

  class Dog
    def talk
      puts "i'm talking"
    end

    def testing_dog
      talk # Class Dog#talk
      t = Test.new
      with t do
        test # Calls Test#test
        @self.talk # Calls Dog#talk
      end
    end
  end

···

--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)

Jim Weirich wrote:

Zach Dennis said:

Also with the with block being how Jamis Buck and David A. Black posted
it works nice with methods as well.

def talk
   puts "i'm talking"
end

def Test
   def test
      puts "i'm testing"
   end
end

t = Test.new
with t do
   talk
   test
end

'talk' is called correctly because it is in the current scope of the
*with* block and 'test' is called correctly because it exists on object
't'.
   
The above works, but some very similar code is broken ... For example:
class Test
   def test
     puts "i'm testing"
   end
end

class Dog
   def talk
     puts "i'm talking"
   end

   def testing_dog
     talk # Class Dog#talk
     t = Test.new
     with t do
       test # Calls Test#test
       talk # ERROR: undefined local variable or method `talk'
     end
   end
end

This is because the second call to talk is not evaluated in the lexical
scope of the calling object Dog, but within the scope of the Test object. Dog#talk exists, but Test#talk does not. (Your example missed the problem
by putting +talk+ into the global scope.

There is an awkward work around. First write +with+ like this:

module Kernel
   def with(obj, &block)
     obj.instance_eval { @self = eval("self", block) }
     obj.instance_eval &block
   end
end

Then reference Dog#talk like this ...

class Dog
   def talk
     puts "i'm talking"
   end

   def testing_dog
     talk # Class Dog#talk
     t = Test.new
     with t do
       test # Calls Test#test
       @self.talk # Calls Dog#talk
     end
   end
end

I see what you mean, I like your solution. It seems quite elegant! What are your thoughts on an RCR for this functionality Jim?

Zach

Hi --

The above works, but some very similar code is broken ... For example:
  class Test
    def test
      puts "i'm testing"
    end
  end

  class Dog
    def talk
      puts "i'm talking"
    end

    def testing_dog
      talk # Class Dog#talk
      t = Test.new
      with t do
        test # Calls Test#test
        talk # ERROR: undefined local variable or method `talk'
      end
    end
  end

This is because the second call to talk is not evaluated in the lexical
scope of the calling object Dog, but within the scope of the Test object.
Dog#talk exists, but Test#talk does not. (Your example missed the problem
by putting +talk+ into the global scope.

There is an awkward work around. First write +with+ like this:

  module Kernel
    def with(obj, &block)
      obj.instance_eval { @self = eval("self", block) }
      obj.instance_eval &block
    end
  end

Then reference Dog#talk like this ...

  class Dog
    def talk
      puts "i'm talking"
    end

    def testing_dog
      talk # Class Dog#talk
      t = Test.new
      with t do
        test # Calls Test#test
        @self.talk # Calls Dog#talk
      end
    end
  end

You could also do:

  def testing_dog
    s = self
    t = Test.new
    talk
    with t do
      test
      s.talk
    end
  end

(i.e., put the burden of providing a reference to self-the-Dog to on
the method, rather than on 'with')

David

···

On Wed, 25 Aug 2004, Jim Weirich wrote:

--
David A. Black
dblack@wobblini.net

> There is an awkward work around. First write +with+ like this:
>
> module Kernel
> def with(obj, &block)
> obj.instance_eval { @self = eval("self", block) }
> obj.instance_eval &block
> end
> end
>

[...]

You could also do:

  def testing_dog
    s = self
    t = Test.new
    talk
    with t do
      test
      s.talk
    end
  end

(i.e., put the burden of providing a reference to self-the-Dog to on
the method, rather than on 'with')

It also has the additional benefit of being thread-safe, or more
precisely not as thread-unsafe :stuck_out_tongue:
Also, polluting the original object with another i.v. feels very wrong.

You could also do

batsman@tux-chan:/tmp$ cat fdgdfg.rb
def with(obj, &block)
    o = Object.new
    s = self
    # I'd use define_method but {|*a,&b| ...} is 1.9 only
    o.instance_eval{ @self = s; @obj = obj }
    class << o
        def method_missing(meth,*a,&b)
            @obj.send(meth, *a, &b)
        end
    end
    o.instance_eval(&block)
end

class Foo
    def foo
        puts "Foo#foo"
        with(Bar.new){ bar; @self.babar{ puts "foo" } }
    end
    def babar
        puts "1931"
        yield
    end
end

class Bar; def bar; puts "Bar#bar" end end

Foo.new.foo

batsman@tux-chan:/tmp$ ruby fdgdfg.rb
Foo#foo
Bar#bar
1931
foo

But I don't really like it.

···

On Thu, Aug 26, 2004 at 06:56:13AM +0900, David A. Black wrote:

--
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com