"once" meta-method without "module_eval" (Pickaxe)

G

Greg Weeks

My impression is that Ruby meta-programming used to lean heavily on
evaluating strings as code, but is moving away from that. (Good!) In
that spirit, it would be nice to replace this example from Chapter 24,
Classes and Objects:

class Date
class <<self
def once(*ids) # :nodoc:
for id in ids
module_eval <<-"end;"
alias_method :__#{id.to_i}__, :#{id.to_s}
private :__#{id.to_i}__
def #{id.to_s}(*args, &block)
(@__#{id.to_i}__ ||= [__#{id.to_i}__(*args, &block)])[0]
end
end;
end
end
end
...
once :as_string, :as_YMD
end

"once" replaces a specified method with a method that caches the return
value on the first call.

The example above served to illustrate the use of class instance
variables. However, the replacement that comes to mind does not:

class <<self
def once(*ids)
values = {}
for id in ids
new_id = "__#{id.to_i}__".to_sym
alias_method new_id, id
private new_id
define_method (id) do |*args|
(values[id] ||= [ method(new_id).call(*args) ])[0]
end
end
end
end

Is there some way to avoid evals and still use class instance variables?

Is there some way to avoid evals and still handle the "&block" argument?

Is there a natural alternative to the local hash variable?

Am I right to think that eval-ing strings is retro?

(By the way, neither implementation allows "once" to be called twice
with the same symbol.)
 
T

Trans

My impression is that Ruby meta-programming used to lean heavily on
evaluating strings as code, but is moving away from that. (Good!) In
that spirit, it would be nice to replace this example from Chapter 24,
Classes and Objects:

class Date
class <<self
def once(*ids) # :nodoc:
for id in ids
module_eval <<-"end;"
alias_method :__#{id.to_i}__, :#{id.to_s}
private :__#{id.to_i}__
def #{id.to_s}(*args, &block)
(@__#{id.to_i}__ ||= [__#{id.to_i}__(*args, &block)])[0]
end
end;
end
end
end
...
once :as_string, :as_YMD
end

"once" replaces a specified method with a method that caches the return
value on the first call.

The example above served to illustrate the use of class instance
variables. However, the replacement that comes to mind does not:

class <<self
def once(*ids)
values = {}
for id in ids
new_id = "__#{id.to_i}__".to_sym
alias_method new_id, id
private new_id
define_method (id) do |*args|
(values[id] ||= [ method(new_id).call(*args) ])[0]
end
end
end
end

Is there some way to avoid evals and still use class instance variables?
instance_variable_get/set

Is there some way to avoid evals and still handle the "&block" argument?

Wait for 1.9.
Is there a natural alternative to the local hash variable?

instance_variable_get/set if you want to do it like the original, but
your local might be better.
Am I right to think that eval-ing strings is retro?

I'm not sure there's anything really wrong with eval. Often the code
looks cleaner compared to these long winded meta-methods.

T.
 
D

Daniel DeLorme

Greg said:
My impression is that Ruby meta-programming used to lean heavily on
evaluating strings as code, but is moving away from that. (Good!)

I have just one question: why is that good? Is it just that avoiding
evaluation of strings somehow "feels cleaner" or is there actually a
justification for that?

If you rememer that load/require basically has the effect of eval'ing
the content of a file, string eval doesn't feel so evil anymore.

Daniel
 
G

Greg Weeks

First, thanks (you all) for the info I asked for.
I'm not sure there's anything really wrong with eval. Often the code
looks cleaner compared to these long winded meta-methods.

My intuition is that a good argument could be given. But I'm not the
person to give it.
not to mention it doesn't create a closure/leak...

The fact that I can use "define-method" to *almost but not quite*
achieve the affect of a "def" is almost certainly a Ruby wart. If I run
into trouble I'll just blame Matz :)
 
P

Phrogz

Am I right to think that eval-ing strings is retro?

My first dynamically-typed, runtime-interpreted scripting language was
JavaScript. In JavaScript, eval is considered a bad idea. Loading a
string of JavaScript code compiles it to bytecode and then executes
the byte code. Hitting an eval() call in the middle of a program
requires booting up the lexer and bytecode compiler again, to handle
that one string, before it can be run. It's inefficient, and almost
never needed.

I gather the same is not true in Ruby currently. I think (but may be
horribly wrong) that this is not so much because of some excellent
implementation of eval, but a because everything is in the same slow
pool of interpretation.

I'm totally guessing, but I would suspect that at some point in Ruby's
runtime future, startup optimizations/compilations will happen once
only. If this is true (and I know very little about virtual machines
and bytecodes and the actual implementation, so I could be wrong),
then I would suspect that eval of a string (not a block) will incur
undesirable overhead.

For these reasons (my history with eval in JS and my suspicion that it
may become undesirable from a performance standpoint) I personally
never eval strings.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top