'def', but with a closure

Discussion in 'Ruby' started by Albert Schlef, Feb 15, 2010.

  1. It occurred to me several times that I wanted to do:

    @cars.each do |owner, model|
    widget = TkLabel.new:)text => model)
    def widget.get_owner
    return owner
    end
    end

    But, of course, this doesn't work because the code inside 'def' doesn't
    see the enveloping variables, so the 'owner' within the 'def' isn't
    recognized.

    So instead I do:

    @cars.each do |owner, model|
    widget = TkLabel.new:)text => model)
    meta = (class << widget; self; end)
    meta.send:)define_method, :get_owner) do
    return owner
    end
    end

    It work. Yet, it looks a bit ugly. Is there any "nicer" way I'm missing?
    --
    Posted via http://www.ruby-forum.com/.
    Albert Schlef, Feb 15, 2010
    #1
    1. Advertising

  2. From: Albert Schlef <>
    Subject: 'def', but with a closure
    Date: Mon, 15 Feb 2010 14:10:36 +0900
    Message-ID: <>
    > It occurred to me several times that I wanted to do:
    >
    > @cars.each do |owner, model|
    > widget = TkLabel.new:)text => model)
    > def widget.get_owner
    > return owner
    > end
    > end
    >
    > But, of course, this doesn't work because the code inside 'def' doesn't
    > see the enveloping variables, so the 'owner' within the 'def' isn't
    > recognized.


    e.g.
    ------------------------------------------------------
    labels = @cars.collect{|owner, model|
    TkLabel.new:)text=>model){ # in this block, self is the created widget.
    @owner = owner
    def get_owner
    @owner
    end
    }
    }

    labels.each{|w| p [w.text, w.get_owner]}
    ------------------------------------------------------
    --
    Hidetoshi NAGAI ()
    Hidetoshi NAGAI, Feb 15, 2010
    #2
    1. Advertising

  3. Albert Schlef

    Josh Cheek Guest

    [Note: parts of this message were removed to make it a legal post.]

    Is there some reason why Ruby hasn't made it possible to just do

    @cars.each do |owner, model|
    widget = TkLabel.new( :text => model )
    widget.define_method( :get_owner ) { owner }
    end

    I find myself conceptually knowing what I want to do, but having to jump
    through lots of hoops to do it, or telling myself that I'm not supposed to
    do

    eval <<-END_OF_METHOD
    def widget.get_owner
    #{owner.inspect}
    end
    END_OF_METHOD

    But if I'm supposed to avoid things like that, then why make them so much
    easier than the alternative? Eval worked first time I tried (granted my test
    case had owner as a string), but I spent maybe 30 min trying to get Albert's
    way to work. Hidetoshi's solution works because of TkLabel's behaviour, but
    can't be expected to work for any object (ie not generalizable).

    Is it supposed to be an act of discouragement, to prevent us from doing
    something dangerous?
    Was it just not considered that most people would want or need to do
    something like this?
    Is it something that is actually very difficult to implement / not possible,
    due to a deeper Ruby model than I understand?
    Is it actually really easy, and I just am slow to catch on?

    Any thoughts?

    Capability like this gets me excited, but the difficulty of implementing it
    makes me frustrated.
    Josh Cheek, Feb 15, 2010
    #3
  4. From: Josh Cheek <>
    Subject: Re: 'def', but with a closure
    Date: Mon, 15 Feb 2010 15:35:44 +0900
    Message-ID: <>
    > way to work. Hidetoshi's solution works because of TkLabel's behaviour, but
    > can't be expected to work for any object (ie not generalizable).


    e.g.
    ------------------------------------------------------
    objs = @cars.collect{|owner, model|
    obj = Object.new
    obj.instance_eval{
    @owner = owner
    @model = model
    def get_model
    @model
    end
    def get_owner
    @owner
    end
    }
    obj
    }

    objs.each{|o| p [o.get_model, o.get_owner]}
    ------------------------------------------------------
    --
    Hidetoshi NAGAI ()
    Hidetoshi NAGAI, Feb 15, 2010
    #4
  5. 2010/2/15 Albert Schlef <>:
    > It occurred to me several times that I wanted to do:
    >
    > = do |owner, model|
    > =A0 =A0widget =3D TkLabel.new:)text =3D> model)
    > =A0 =A0def widget.get_owner
    > =A0 =A0 =A0return owner
    > =A0 =A0end
    > =A0end
    >
    > But, of course, this doesn't work because the code inside 'def' doesn't
    > see the enveloping variables, so the 'owner' within the 'def' isn't
    > recognized.
    >
    > So instead I do:
    >
    > = do |owner, model|
    > =A0 =A0widget =3D TkLabel.new:)text =3D> model)
    > =A0 =A0meta =3D (class << widget; self; end)
    > =A0 =A0meta.send:)define_method, :get_owner) do
    > =A0 =A0 =A0return owner
    > =A0 =A0end
    > =A0end
    >
    > It work. Yet, it looks a bit ugly. Is there any "nicer" way I'm missing?


    You can at least shorten it a bit

    @cars.each do |owner, model|
    widget =3D TkLabel.new:)text =3D> model)
    (class << widget; self; end).send:)define_method, :get_owner) do
    owner
    end
    end

    I find this better than other approaches because storing in an
    instance variable is not the same as using the closure. Josh's
    approach with eval has the disadvantage of a) using eval in the first
    place, b) it does not work for all types of objects handed in as owner
    and c) for Strings it's less efficient because it will create a new
    String instance every time the method is invoked.

    Kind regards

    robert

    --=20
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
    Robert Klemme, Feb 15, 2010
    #5
  6. Robert Klemme wrote:
    > You can at least shorten it a bit
    >
    > @cars.each do |owner, model|
    > widget = TkLabel.new:)text => model)
    > (class << widget; self; end).send:)define_method, :get_owner) do
    > owner
    > end
    > end


    You can use a block (non-string) eval, which isn't quite as short but
    avoids the 'send'. It's nicer if you have a whole bunch of define_method
    calls.

    @cars.each do |owner, model|
    widget = TkLabel.new:)text => model)
    (class << widget; self; end).class_eval do
    define_method:)get_owner) do
    owner
    end
    end
    end

    Or use an explicit instance variable instead of a closure.

    module Owner
    def get_owner
    @owner
    end
    end

    @cars = {"jim"=>"KA", "fred"=>"Porsche", "trunky"=>"Mini"}
    @cars.each do |owner, model|
    widget = TkLabel.new:)text => model)
    widget.extend Owner
    widget.instance_variable_set:)@owner, owner)
    # or: widget.instance_eval { @owner = owner }
    # or: widget.set_owner(owner)
    end
    --
    Posted via http://www.ruby-forum.com/.
    Brian Candler, Feb 15, 2010
    #6
  7. Hidetoshi NAGAI wrote:
    > objs = @cars.collect{|owner, model|
    > obj = Object.new
    > obj.instance_eval{
    > @owner = owner
    > @model = model
    > def get_model
    > @model
    > end
    > def get_owner
    > @owner
    > end
    > }
    > obj
    > }


    Thanks. That's useful.

    I wouldn't have imagined `def` inside instance_eval adds the method to
    the singleton.

    BTW, anybody knows what exactly `def` does? Until now I thought it does
    this:

    Lookup the enclosing class (or module) and add the method
    to its m_tbl.

    Based on Hidetoshi's code it seems `def` does this:

    Look at 'self'. If it's a class (or a module), add the method
    to its m_tbl. If it isn't, get its singleton and add the method
    to *its* m_tbl.

    --
    Posted via http://www.ruby-forum.com/.
    Albert Schlef, Feb 16, 2010
    #7
  8. Robert Klemme wrote:
    > Josh's approach with eval has the disadvantage of [...]


    It has another disadvantage: It's possible to imagine a Ruby
    implementation that doesn't have a compiler at run-time.

    It's surprising to see that a few "important" libraries are using eval.
    ActiveRecord, for example. I wonder why.
    --
    Posted via http://www.ruby-forum.com/.
    Albert Schlef, Feb 16, 2010
    #8
  9. Brian Candler wrote:
    > You can use a block (non-string) eval, which isn't quite as short but
    > avoids the 'send'. It's nicer if you have a whole bunch of define_method
    > calls.
    >
    > @cars.each do |owner, model|
    > widget = TkLabel.new:)text => model)
    > (class << widget; self; end).class_eval do
    > define_method:)get_owner) do
    > owner
    > end
    > end
    > end


    Thanks. I tried to shorten it to:

    @cars.each do |owner, model|
    widget = TkLabel.new:)text => model)
    class << widget
    define_method:)get_owner) do
    owner
    end
    end
    end

    But it turns out that 'class', just like 'def', cuts off the inner code
    from the variables outside.

    --
    Posted via http://www.ruby-forum.com/.
    Albert Schlef, Feb 16, 2010
    #9
  10. Albert Schlef wrote:
    > BTW, anybody knows what exactly `def` does? Until now I thought it does
    > this:
    >
    > Lookup the enclosing class (or module) and add the method
    > to its m_tbl.
    >
    > Based on Hidetoshi's code it seems `def` does this:
    >
    > Look at 'self'. If it's a class (or a module), add the method
    > to its m_tbl. If it isn't, get its singleton and add the method
    > to *its* m_tbl.


    I believe it's slightly more complex than that.

    In executing code, you're familiar with the idea of "the current
    object". This is made available as 'self'.

    However there's also a more hidden concept of "the current class". It's
    rather difficult to get hold of this, but it's where def defines
    instance methods.

    "The current class" is set inside a class ... end construct. But as
    you've found, this starts a new scope. So you can use class_eval
    instead, which sets both the current object and the current class.

    Compare:

    class Foo; end
    Foo.instance_eval do
    def foo; puts "XXX!"; end
    end
    Foo.foo # you have made a class method

    class Bar; end
    Bar.class_eval do
    def bar; puts "YYY!"; end
    end
    Bar.new.bar # you have made an instance method

    Anyway, this is moot if you are working with closures, because 'def'
    also starts a fresh scope.

    > It's surprising to see that a few "important" libraries are using eval.
    > ActiveRecord, for example. I wonder why.


    'def' methods are more efficient at runtime (and also less liable to
    unforeseen side-effects), precisely because they do not have access to
    outer scopes. If you want to 'def <foo>' where <foo> is dynamic, you
    have to use eval.
    --
    Posted via http://www.ruby-forum.com/.
    Brian Candler, Feb 16, 2010
    #10
  11. Brian Candler wrote:
    > "The current class" is set inside a class ... end construct. But as
    > you've found, this starts a new scope. So you can use class_eval
    > instead, which sets both the current object and the current class.


    Note that instance_eval sets the current class to the object's singleton
    class, whereas class_eval sets the current class to the object itself,
    if that object is a Class.

    So this works:

    @cars.each do |owner, model|
    widget = TkLabel.new:)text => model)
    widget.instance_eval do
    def get_owner
    "nobody"
    end
    end
    end

    Unfortunately, define_method is a method of Module, not Object. So this
    doesn't work:

    @cars.each do |owner, model|
    widget = TkLabel.new:)text => model)
    widget.instance_eval do
    define_method:)get_owner) do
    owner
    end
    end
    end

    Hence the need for (class << widget; self; end).class_eval

    Regards,

    Brian.
    --
    Posted via http://www.ruby-forum.com/.
    Brian Candler, Feb 16, 2010
    #11
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Jiong Feng
    Replies:
    0
    Views:
    819
    Jiong Feng
    Nov 19, 2003
  2. Sean Ross
    Replies:
    3
    Views:
    123
    Aredridel
    Dec 25, 2003
  3. Replies:
    7
    Views:
    158
  4. planetthoughtful

    Newbie: def must come before call to def?

    planetthoughtful, Mar 12, 2007, in forum: Ruby
    Replies:
    4
    Views:
    126
    Pit Capitain
    Mar 12, 2007
  5. Julian Mehnle
    Replies:
    0
    Views:
    238
    Julian Mehnle
    Jul 17, 2003
Loading...

Share This Page