Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perils and caveats of Kernel::eval in Ruby?

Tags:

ruby

eval

I'm using Ruby 1.9.2 p180.

I'm writing a continuous evaluation tool for Rubyvis (to be part of SciRuby). Basically, you set up the Rubyvis::Panel in your input file (e.g., test.rb), and this SciRuby class (Plotter) watches test.rb for modifications. When there's a change, SciRuby runs the script through eval.

The script works if I run it from the command line, but when executed through eval, the plot is wrong -- a straight line, as if all the data is gone, instead of what you see here. Note: Previously, it said here that the SVG was different -- but it turns out this was the result of REXML being loaded instead of nokogiri.

Here are the test scripts and eval code. Most produce the straight line (with exceptions described in the edit section below).

I haven't the faintest how this is happening.

I have a few ideas as to why it might be happening, but no clue as to the mechanism.

Hypotheses:

  1. eval doesn't allow deep copies to be made. Objects that are taken from eval are missing pieces in certain contexts, particularly when lambda is used to process the data into the correct format for the plot.
  2. For some reason, eval is not honoring the bundled dep list when require is called -- maybe the wrong version of nokogiri is being used in my binding?
  3. Some other required library (perhaps RSVG?) has overloaded some method Rubyvis uses.

Has anyone seen anything like this before? I'm sort of feeling around in the dark -- at a total loss as to where to begin troubleshooting.

Edit 9/15/11: new information

It seems that the call to OpenStruct.new is causing the problem.

If I define data as a list of lists, data = pv.range(0,10,0.1).map { |d| [d,Math.sin(d)+2+rand()] }, it works well.

But when data is defined as a list of OpenStructs, the following code gives incorrect output:

data = pv.range(0, 10, 0.1).map {|x|
  o = OpenStruct.new({:x=> x, :y=> Math.sin(x) + 2+rand()})
  STDERR.puts o.inspect # Output is correct.
  o
}
STDERR.puts "size of data: #{data.size}"
STDERR.puts "first x = #{data.first.x}" # Output is first x = 0.0
STDERR.puts "first y = #{data.first.y}" # Output is first y =     (WRONG)

I can even induce an error if I use collect when assigning the data, e.g.,

vis.add(pv.Line).data(data.collect { |d| [d.x,d.y] }

plotter.rb:88:in `block in <main>': undefined method `x' for [0.0, nil]:Array (NoMethodError)

versus no error with vis.add(pv.Line).data(data). The error appears to originate from the call to eval("vis.render()", bind) in my app's source (not in the plot script).

It turns out that if I just use a hash, e.g., {:x => x, :y => Math.sin(x)}, that works fine. But when I explicitly say Hash.new({:x => x, :y => Math.sin(x)}), that gives an error regardless of how I call vis.data:

rubyvis/lib/rubyvis/internals.rb:184:in `each': comparison of Hash with Hash failed (ArgumentError)

So the difference is in how my data is assigned. The question is: why?

Copies of the inputs are available in the original gist. Thanks for your help.

like image 785
Translunar Avatar asked Nov 13 '22 17:11

Translunar


1 Answers

It turns out that if I just use a hash, e.g., {:x => x, :y => Math.sin(x)}, that works fine. But when I explicitly say Hash.new({:x => x, :y => Math.sin(x)}), that gives an error regardless of how I call vis.data:

First of all, your call to Hash.new is wrong. Hash.new takes a parameter that's the default value of the hash.

like image 97
Dogbert Avatar answered Dec 01 '22 01:12

Dogbert