Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > instance_eval with local variable

Reply
Thread Tools

instance_eval with local variable

 
 
Phrogz
Guest
Posts: n/a
 
      01-12-2010
I have an object with an instance variable:
obj = Object.new
obj.instance_variable_set :@who, "foo"

I have a proc with arity 1:
@who= "main"
holla = lambda{ |str| p @who, str }

I want to evaluate the lambda inside the scope of obj, but also pass
in a local variable. I can't figure out how to do this (or if it's
even possible).

I can't instance_eval the proc in the context of obj, because the proc
has arity:
obj.instance_eval( &holla )
#=> ArgumentError: wrong number of arguments (0 for 1)

And just invoking the proc within the context of obj doesn't set the
scope:
obj.instance_eval{ holla[ "hoorah" ] }
#=> "main"
#=> "hoorah"

Any sweet meta trickery you can think of to accomplish what I want?

For those interested in second-guessing my approach and coming up with
a better way to achieve the same end goal, the application is a
documentation system which converts markup (markdown, textile, haml,
etc.) into CHM. I have a system for registering an arbitrary number of
HTML post-processors with the application. The post-processors
associate a regexp with a proc that converts the matched code:

module DocuBot
@snippets = {}
def self.handle_snippet( regexp, &handler )
@snippets[ regexp ] = handler
end
def self.process_snippets( html )
@snippets.inject(html){ |h,(regexp,handler)| h.gsub( regexp,
&handler ) }
end
end

...

# Represents an entire documentation site
class DocuBot::Bundle
def write_html
@pages.each do |page|
html = DocuBot.process_snippets( page.to_html )
...
end
end
end

...

# Mark $$foo$$ as glossary items.
DocuBot.handle_snippet /\$\$\w[^$]+\$\$/ do |match|
# TODO: look up the glossary term in the bundle's glossary
# and insert appropriate text, or flag a new glossary term
"<span class='glossary'>#{match[2..-3]}</span>"
end

This works fine, except for the TODO above. I want to be able to
instance_eval that block within the scope of a particular bundle so I
can look at @glossary before returning the correct final HTML for gsub
to use.
 
Reply With Quote
 
 
 
 
Phrogz
Guest
Posts: n/a
 
      01-12-2010
On Jan 12, 12:23*pm, Phrogz <phr...@mac.com> wrote:
[snip]
> For those interested in second-guessing my approach and coming up with
> a better way to achieve the same end goal

FWIW, there is a workaround I've come up with. Instead of trying to
evaluate the proc in the scope and access instance variables directly,
I can pass the object to the proc and work from its public interface.
My original code is thus changed:

> def self.process_snippets( html )
> @snippets.inject(html){ |h,(regexp,handler)| h.gsub( regexp, &handler )}

def self.process_snippets( html, bundle )
@snippets.inject(html){ |h,(r,proc)| h.gsub(r){ |s| proc
[s,bundle] } }


> * # Mark $$foo$$ as glossary items.
> * DocuBot.handle_snippet /\$\$\w[^$]+\$\$/ do |match|
> * * # TODO: look up the glossary term in the bundle's glossary
> * * # and insert appropriate text, or flag a new glossary term
> * * "<span class='glossary'>#{match[2..-3]}</span>"
> * end

DocuBot.handle_snippet /\$\$\w[^$]+\$\$/ do |match, bundle|
term = match[2..-3]
defn = bundle.glossary[term]
* "<span class='glossary' title='#{defn}'>#{term}</span>"
end


I'm thus unblocked in my endeavors, but still interested in the
metaprogramming needed to accomplish my original goal. I was actually
a little surprised that instance_eval doesn't take an arbitrary number
of arguments in the block form and splat them along to the block's
parameters.
 
Reply With Quote
 
 
 
 
pharrington
Guest
Posts: n/a
 
      01-12-2010
On Jan 12, 2:57*pm, Phrogz <phr...@mac.com> wrote:
> On Jan 12, 12:23*pm, Phrogz <phr...@mac.com> wrote:
> [snip]> For those interested in second-guessing my approach and coming upwith
> > a better way to achieve the same end goal

>
> FWIW, there is a workaround I've come up with. Instead of trying to
> evaluate the proc in the scope and access instance variables directly,
> I can pass the object to the proc and work from its public interface.
> My original code is thus changed:
>
> > def self.process_snippets( html )
> > * @snippets.inject(html){ |h,(regexp,handler)| h.gsub( regexp, &handler ) }

>
> def self.process_snippets( html, bundle )
> * @snippets.inject(html){ |h,(r,proc)| h.gsub(r){ |s| proc
> [s,bundle] } }
>
> > * # Mark $$foo$$ as glossary items.
> > * DocuBot.handle_snippet /\$\$\w[^$]+\$\$/ do |match|
> > * * # TODO: look up the glossary term in the bundle's glossary
> > * * # and insert appropriate text, or flag a new glossary term
> > * * "<span class='glossary'>#{match[2..-3]}</span>"
> > * end

>
> DocuBot.handle_snippet /\$\$\w[^$]+\$\$/ do |match, bundle|
> * term = match[2..-3]
> * defn = bundle.glossary[term]
> * "<span class='glossary' title='#{defn}'>#{term}</span>"
> end
>
> I'm thus unblocked in my endeavors, but still interested in the
> metaprogramming needed to accomplish my original goal. I was actually
> a little surprised that instance_eval doesn't take an arbitrary number
> of arguments in the block form and splat them along to the block's
> parameters.


Well, I'm not sure if this is the particular sort of nastiness you're
looking for, but.... defining a new method with the proc works..

xeno@amrita:~/projects $ cat nasty.rb
obj = Object.new
obj.instance_variable_set :@who, "foo"
@who = "main"
holla = lambda {|str| p @who, str}

class << obj
def eigen
class << self; self; end
end
end

obj.eigen.send(:define_method, :holla, &holla)
obj.holla "horrah"
xeno@amrita:~/projects $ ruby nasty.rb
"foo"
"horrah"
 
Reply With Quote
 
pharrington
Guest
Posts: n/a
 
      01-12-2010
On Jan 12, 3:04*pm, pharrington <xenogene...@gmail.com> wrote:
> On Jan 12, 2:57*pm, Phrogz <phr...@mac.com> wrote:
>
>
>
>
>
> > On Jan 12, 12:23*pm, Phrogz <phr...@mac.com> wrote:
> > [snip]> For those interested in second-guessing my approach and coming up with
> > > a better way to achieve the same end goal

>
> > FWIW, there is a workaround I've come up with. Instead of trying to
> > evaluate the proc in the scope and access instance variables directly,
> > I can pass the object to the proc and work from its public interface.
> > My original code is thus changed:

>
> > > def self.process_snippets( html )
> > > * @snippets.inject(html){ |h,(regexp,handler)| h.gsub( regexp, &handler ) }

>
> > def self.process_snippets( html, bundle )
> > * @snippets.inject(html){ |h,(r,proc)| h.gsub(r){ |s| proc
> > [s,bundle] } }

>
> > > * # Mark $$foo$$ as glossary items.
> > > * DocuBot.handle_snippet /\$\$\w[^$]+\$\$/ do |match|
> > > * * # TODO: look up the glossary term in the bundle's glossary
> > > * * # and insert appropriate text, or flag a new glossary term
> > > * * "<span class='glossary'>#{match[2..-3]}</span>"
> > > * end

>
> > DocuBot.handle_snippet /\$\$\w[^$]+\$\$/ do |match, bundle|
> > * term = match[2..-3]
> > * defn = bundle.glossary[term]
> > * "<span class='glossary' title='#{defn}'>#{term}</span>"
> > end

>
> > I'm thus unblocked in my endeavors, but still interested in the
> > metaprogramming needed to accomplish my original goal. I was actually
> > a little surprised that instance_eval doesn't take an arbitrary number
> > of arguments in the block form and splat them along to the block's
> > parameters.

>
> Well, I'm not sure if this is the particular sort of nastiness you're
> looking for, but.... defining a new method with the proc works..
>
> xeno@amrita:~/projects *$ cat nasty.rb
> obj = Object.new
> obj.instance_variable_set :@who, "foo"
> @who = "main"
> holla = lambda {|str| p @who, str}
>
> class << obj
> * def eigen
> * * class << self; self; end
> * end
> end
>
> obj.eigen.send(:define_method, :holla, &holla)
> obj.holla "horrah"
> xeno@amrita:~/projects *$ ruby nasty.rb
> "foo"
> "horrah"


FWIW, I tend to only uses procs when metaprogramming when I want their
closure properties. Otherwise, pretty much everything else Ruby offers
is a better fit.
 
Reply With Quote
 
Brian Candler
Guest
Posts: n/a
 
      01-12-2010
Gavin Kistner wrote:
> I have a proc with arity 1:
> @who= "main"
> holla = lambda{ |str| p @who, str }


I believe your lambda is bound to the @who in the scope where it was
defined, you can't dynamically rebind it.

To demonstrate:

class A
def initialize
@x = 123
end
def run
fn = lambda { puts "@x = #{@x}" }
B.new(fn).run
end
end
class B
def initialize(fn)
@x = 456
@fn = fn
end
def run
@fn.call
end
end

A.new.run # => @x = 123

In other words, @who is not the same as
self.instance_variable_get(:@who), as far as I can see anyway.

I think that what you ask for would lead to very surprising behaviour,
especially when using blocks:

@count = 0
something.whatever { @who += 1 }

You wouldn't want @who ever to be bound to some other object further
down the call chain.

> For those interested in second-guessing my approach and coming up with
> a better way to achieve the same end goal


Perhaps rather than using lambdas, define methods on the object which
contains the instance variables you're interested in, then call those
methods (or use method(:name) to get a Method object, which you can pass
around pretty much like a lambda)
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
Phrogz
Guest
Posts: n/a
 
      01-12-2010
On Jan 12, 1:19*pm, Brian Candler <b.cand...@pobox.com> wrote:
> I believe your lambda is bound to the @who in the scope where it was
> defined, you can't dynamically rebind it.


Nope:
>cat tmp.rb

@who = "main"
proc = ->{ p @who }
proc[]

bar = Object.new; bar.instance_variable_set :@who, "bar"
bar.instance_eval( &proc )

>ruby -v tmp.rb

ruby 1.9.1p243 (2009-07-16 revision 24175) [i386-mingw32]
"main"
"bar"

instance_eval can rebind the proc, but doesn't let you pass arguments
to it.

> I think that what you ask for would lead to very surprising behaviour,
> especially when using blocks:
>
> * *@count = 0
> * *something.whatever { @who += 1 }


Sure, the concept of a closure is great and 99% of the time should be
preserved. I wasn't asking for my edge-case behavior to be the norm,
but rather trying to figure if it's possible at all.


> > For those interested in second-guessing my approach and coming up with
> > a better way to achieve the same end goal

>
> Perhaps rather than using lambdas, define methods on the object which
> contains the instance variables you're interested in, then call those
> methods (or use method(:name) to get a Method object, which you can pass
> around pretty much like a lambda)


Interesting. Slight downside is the requirement that all plug-ins have
unique method names. Or...hrm, perhaps some insanity involving
define_method on the receiving end of the proc. Thanks for the
suggestion, anyhow.
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Where does instance_eval _magically_ set the variable to? Andrew Chen Ruby 12 02-22-2008 11:32 AM
Instance Variable v.s. Local Variable Jerry Java 5 08-06-2005 04:40 AM
reference variable and local variable Kench C++ 2 06-28-2004 05:49 PM
Instance Variable vs Local Variable Paul Carey Java 3 12-03-2003 05:05 PM
a static local variable in a static method is thread local storage? Patrick Hoffmann C++ 3 08-08-2003 02:37 PM



Advertisments
 



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57