Too much eval evil? (tell me why I shouldn't do this) 
Author Message
 Too much eval evil? (tell me why I shouldn't do this)

I've been doodling with Ruby (experimenting with it in order to figure
out what it's capable of) like a kid with crayons... ;-)

So here I present some thoughts and I request a critique.

Let's say I've got a string which has some ruby variables in it and I
want each of those variables to take on a range of values and then I
want to evaluate that string given every combination of those values
(a cartesian product).  And of course I want to be able to use this
with a variable number of varying variables, so my cartesian product
function has to work given any number of variables.

Since you get a cartesian product of a fixed number of arrays by
doing:
#say we have three arrays:
a_ary.each { |a|
   b_ary.each { |b|
      c_ary.each { |c|
         puts "a=#{a}, b=#{b}, c=#{c}
      }
   }

Quote:
}

That's all well and good if you know up front that you'll only have
three arrays to deals with, but if you don't....  My idea was to
create the cartesian product function dynamically and to an
instance_eval of it.  I present the code below.  Please let me know if
this is a crazy idea or not and also present alternatives:

#here's the string to interpolate:
str = 'width is: #{width}, length is: #{length}, hight is: #{hight}'

hash = {
   "width" => (0..20).to_a,
   "length" => (43 .. 150).to_a,
   "hight" => ['z','yz','xyz','xz','zz']

Quote:
}

def make_code(f_str,variables)
   var = variables.shift
   f_str << "   #{var}_i.each { |#{var}|\n"
   if variables.empty?
      f_str << '      puts eval \'"\'+str+\'"\' '
      f_str << "\n"
   else
      make_code(f_str,variables)
   end
   f_str << "   }\n"
end

def gen_cart_prod(hashOfarrays,str)
   i=0
   func_str =  "def cartesian_prod(hashOfarrays,str)\n"
   hashOfarrays.each {|var,array|
      func_str << "  #{var}_i = hashOfarrays[\"#{var}\"]\n"
   }
   make_code(func_str,hashOfarrays.keys)
   func_str << "end\n"        
puts func_str
   instance_eval func_str
   cartesian_prod(hashOfarrays,str)
end

gen_cart_prod(hash,str)
#end

gandy



Wed, 21 Apr 2004 06:30:59 GMT  
 Too much eval evil? (tell me why I shouldn't do this)
Hello --

Quote:

> I've been doodling with Ruby (experimenting with it in order to figure
> out what it's capable of) like a kid with crayons... ;-)

> So here I present some thoughts and I request a critique.

> Let's say I've got a string which has some ruby variables in it and I
> want each of those variables to take on a range of values and then I
> want to evaluate that string given every combination of those values
> (a cartesian product).  And of course I want to be able to use this
> with a variable number of varying variables, so my cartesian product
> function has to work given any number of variables.

> Since you get a cartesian product of a fixed number of arrays by
> doing:
> #say we have three arrays:
> a_ary.each { |a|
>    b_ary.each { |b|
>       c_ary.each { |c|
>          puts "a=#{a}, b=#{b}, c=#{c}
>       }
>    }
> }

> That's all well and good if you know up front that you'll only have
> three arrays to deals with, but if you don't....  My idea was to
> create the cartesian product function dynamically and to an
> instance_eval of it.  I present the code below.  Please let me know if
> this is a crazy idea or not and also present alternatives:

I don't have a full-blown alternative, but just a suggestion: have you
looked at the Matrix class?  (matrix.rb in the Ruby distribution) I'm
not sure whether it is relevant, but you might have a look.

David

--
David Alan Black


Web:  http://pirate.shu.edu/~blackdav



Wed, 21 Apr 2004 07:44:28 GMT  
 Too much eval evil? (tell me why I shouldn't do this)

There's a cartesian product operator in
http://www.ruby-lang.org/en/raa-list.rhtml?name=Enumerable+tools

Click on op.html on the project page for documentation.

--
Joel VanderWerf                          California PATH, UC Berkeley

http://www.path.berkeley.edu                       FAX (510) 231-9512



Wed, 21 Apr 2004 07:49:35 GMT  
 Too much eval evil? (tell me why I shouldn't do this)

Quote:
> That's all well and good if you know up front that you'll only have
> three arrays to deals with, but if you don't....  My idea was to
> create the cartesian product function dynamically and to an
> instance_eval of it.  I present the code below.  Please let me know if
> this is a crazy idea or not and also present alternatives:

I've done something similar without doing any evals. It took a bit more than
I thought up front, but I think it read better than your example. Right now,
I'm typing one-handed here while feeding one of my baby boys, so this is all
you get for the moment. <g> ... maybe it'll spur you on to other ideas...

Chris



Wed, 21 Apr 2004 07:54:36 GMT  
 Too much eval evil? (tell me why I shouldn't do this)

Quote:
> Let's say I've got a string which has some ruby variables in it and I
> want each of those variables to take on a range of values and then I
> want to evaluate that string given every combination of those values
> (a cartesian product).  And of course I want to be able to use this
> with a variable number of varying variables, so my cartesian product
> function has to work given any number of variables.

This was just too interesting of a challenge for me to pass up.  Here is
my version (with no evaling):

# I chose the name "cascade" since we are cascading down through the
array
def cascade(a, list = [], &block)
  return list if list.size == a.size
  a[list.size].each do |item|
         list << item
         cascade(a, list, &block)
         if list.size == a.size
                block.call(list)
         end
         list.slice!(-1)
  end
  list
end

a = [(0..20), (43..150), ['z','yz','xyz','xz','zz']]
cascade(a) do |list|
  puts "width is: #{list[0]}, length is: #{list[1]}, hight is:
#{list[2]}"
end

This runs about twice as fast as the one Thomas originally wrote.  It
could be probably be cleaned up, but it seems to work pretty well.
Using an array instead of a hash guarantees the order in which the
variables will be evaluated.  Being able to call a block gives you more
flexibility than just evaling a string.  Also, when dealing with ranges
you don't really need to turn them into arrays...they respond to "each"
correctly.

Ryan Leavengood



Wed, 21 Apr 2004 08:15:37 GMT  
 Too much eval evil? (tell me why I shouldn't do this)

Quote:

>> Let's say I've got a string which has some ruby variables in it and I
>> want each of those variables to take on a range of values and then I
>> want to evaluate that string given every combination of those values
>> (a cartesian product).  And of course I want to be able to use this
>> with a variable number of varying variables, so my cartesian product
>> function has to work given any number of variables.

>This was just too interesting of a challenge for me to pass up.  Here is
>my version (with no evaling):

># I chose the name "cascade" since we are cascading down through the
>array
>def cascade(a, list = [], &block)
>  return list if list.size == a.size
>  a[list.size].each do |item|
>     list << item
>     cascade(a, list, &block)
>     if list.size == a.size
>            block.call(list)
>     end
>     list.slice!(-1)
>  end
>  list
>end

>a = [(0..20), (43..150), ['z','yz','xyz','xz','zz']]
>cascade(a) do |list|
>  puts "width is: #{list[0]}, length is: #{list[1]}, hight is:
>#{list[2]}"
>end

>This runs about twice as fast as the one Thomas originally wrote.  It
>could be probably be cleaned up, but it seems to work pretty well.
>Using an array instead of a hash guarantees the order in which the
>variables will be evaluated.  Being able to call a block gives you more
>flexibility than just evaling a string.  Also, when dealing with ranges
>you don't really need to turn them into arrays...they respond to "each"
>correctly.

I like Ryan's solution better than the one proposed by Thomas (it seems a
lot cleaner), but I can see that Thomas' solution has an advantage:  What
if one were using some
kind of templating system where you have a file which contains embedded
Ruby code  (like the one I proposed a few days back), then you actually
need to have the names of the variables produced in the cartesian
product code match the names of the variables in the template file - it
looks like the original solution (with a few
modifications) could do that... I think I'll try it.  

This thread turned out to be surprisingly timely, I was just thinking of
some way of doing something similar to this in conjunction with my
Template system for generating test code.

Phil



Wed, 21 Apr 2004 15:12:01 GMT  
 Too much eval evil? (tell me why I shouldn't do this)

Quote:
> I like Ryan's solution better than the one proposed by Thomas (it seems a
> lot cleaner), but I can see that Thomas' solution has an advantage:  What
> if one were using some kind of templating system where you have a file
> which contains embedded Ruby code  (like the one I proposed a few days
> back), then you actually need to have the names of the variables produced
> in the cartesian product code match the names of the variables in the
> template file - it looks like the original solution (with a few
> modifications) could do that... I think I'll try it.

This version allows you to use your own names:

def cascade(a, list = [], &block)
  return list if list.size == a.size
  a[list.size].each do |item|
    list << item
    cascade(a, list, &block)
    if list.size == a.size
      block.call(*list) # Expand the list using *
    end
    list.slice!(-1)
  end
end

a = [(0..20), (43..150), ['z','yz','xyz','xz','zz']]
cascade(a) do |width, length, hight|
  puts "width is: #{width}, length is: #{length}, hight is: #{hight}"
end

But overall I think Joel VanderWerf's Enumerable tools are probably better
thought out and implemented than my version.  I'll probably be adding that
to my list of useful libraries.

Ryan Leavengood



Thu, 22 Apr 2004 01:06:30 GMT  
 Too much eval evil? (tell me why I shouldn't do this)
...
Quote:
> this is a crazy idea or not and also present alternatives:

Hi,

a while back Ben and Me had a thread about this. Your final
solution was somewhere around the lines of

class Array
  def  multi_each
    __multi_each__(*self) {|arg| yield arg } unless empty?
  end
  private    
  def  __multi_each__(arg,*more_args)
    (more_args.empty?) ?
       arg.each { |r| yield [r] } :
       arg.each {|l| __multi_each__(*more_args) {|r| yield [l]+ r }}
  end
end

[0..10, 43..46, ['z','yz','xyz','xz','zz']].multi_each { |list|  
p "width is: #{list[0]}, length is: #{list[1]}, hight is: #{list[2]}" }



Thu, 22 Apr 2004 03:10:18 GMT  
 Too much eval evil? (tell me why I shouldn't do this)

Quote:

> a = [(0..20), (43..150), ['z','yz','xyz','xz','zz']]
> cascade(a) do |width, length, hight|
>   puts "width is: #{width}, length is: #{length}, hight is: #{hight}"
> end

> But overall I think Joel VanderWerf's Enumerable tools are probably better
> thought out and implemented than my version.  I'll probably be adding that
> to my list of useful libraries.

There's something a little different about the product operator I
defined. It returns a new "proxy" enumerable which delegates to the
given ones (the factors of the product). So, in the current version
(**), you can't just pass a block to it, you have to call each on the
return value of product:

  require 'enum/op'
  include EnumerableOperator

  a = [(0..20), (43..150), ['z','yz','xyz','xz','zz']]
  product(*a).each do |width, length, hight|
    puts "width is: #{width}, length is: #{length}, hight is: #{hight}"
  end

However, this allows use of 'for', which is very readable:

  for width, length, hight in product(*a)
    puts "width is: #{width}, length is: #{length}, hight is: #{hight}"
  end

Also, you can chain with other iterations without generating the
intermediate collection, which may be large:

  product(*a).select { |w, l, h| w > 18 && l < 45 && h == 'zz' }

----
(**) With a small change, you can pass a block and have product behave
like cascade. Just define product as follows:

  def product(*factors, &block)
    if block
      Product.new(*factors).each &block
    else
      Product.new(*factors)
    end
  end

Now you can leave out the ".each":

  product(*a) do |width, length, hight|
    puts "width is: #{width}, length is: #{length}, hight is: #{hight}"
  end

This'll go in the next version.

--
Joel VanderWerf                          California PATH, UC Berkeley

http://www.path.berkeley.edu                       FAX (510) 231-9512



Thu, 22 Apr 2004 03:51:10 GMT  
 Too much eval evil? (tell me why I shouldn't do this)

Quote:


> I like Ryan's solution better than the one proposed by Thomas (it seems a
> lot cleaner), but I can see that Thomas' solution has an advantage:  What
> if one were using some
> kind of templating system where you have a file which contains embedded
> Ruby code  (like the one I proposed a few days back), then you actually
> need to have the names of the variables produced in the cartesian
> product code match the names of the variables in the template file - it
> looks like the original solution could do that.

exactly.  If I was passing in a list of strings which had ruby variables to
be interpolated then I need to have the variable names available.

-gandy



Thu, 22 Apr 2004 04:33:54 GMT  
 Too much eval evil? (tell me why I shouldn't do this)


Quote:

>> But overall I think Joel VanderWerf's Enumerable tools are probably better
>> thought out and implemented than my version.  I'll probably be adding that
>> to my list of useful libraries.

>There's something a little different about the product operator I
>defined. It returns a new "proxy" enumerable which delegates to the
>given ones (the factors of the product). So, in the current version
>(**), you can't just pass a block to it, you have to call each on the
>return value of product:

>  require 'enum/op'
>  include EnumerableOperator

>  a = [(0..20), (43..150), ['z','yz','xyz','xz','zz']]
>  product(*a).each do |width, length, hight|
>    puts "width is: #{width}, length is: #{length}, hight is: #{hight}"
>  end

>However, this allows use of 'for', which is very readable:

>  for width, length, hight in product(*a)
>    puts "width is: #{width}, length is: #{length}, hight is: #{hight}"
>  end

>Also, you can chain with other iterations without generating the
>intermediate collection, which may be large:

>  product(*a).select { |w, l, h| w > 18 && l < 45 && h == 'zz' }

>----
>(**) With a small change, you can pass a block and have product behave
>like cascade. Just define product as follows:

>  def product(*factors, &block)
>    if block
>      Product.new(*factors).each &block
>    else
>      Product.new(*factors)
>    end
>  end

>Now you can leave out the ".each":

>  product(*a) do |width, length, hight|
>    puts "width is: #{width}, length is: #{length}, hight is: #{hight}"
>  end

This is all very useful and cool.... any possiblity that it will end up in
Ruby's built-in library?

Phil



Thu, 22 Apr 2004 04:59:41 GMT  
 Too much eval evil? (tell me why I shouldn't do this)

Quote:

> It might be nice to have versions of collect and select and so on that
> _do_ delegate, so that you can chain them more efficiently, like a pipe,
> without generating large temporary arrays:

>   (0..10000).delegate_collect { |x| x**2 }.select { |y| y > 1000 && y <
> 2000 }

How silly of me. The Enumerable tools package includes Enumerable#pipe
which does just this.

--
Joel VanderWerf                          California PATH, UC Berkeley

http://www.path.berkeley.edu                       FAX (510) 231-9512



Thu, 22 Apr 2004 07:20:53 GMT  
 
 [ 13 post ] 

 Relevant Pages 

1. why eval is evil

2. Why exceptions shouldn't be used for flow control [Re: YAS to the

3. Why you shouldn't use -activebackground

4. eval is evil (for spreading list-arguments)

5. can anyone please tell me why this won't work

6. Why doesn't this eval...??

7. Why Tcl/Tk is evil (was: Re: Linux/Unix versus NT)

8. Shouldn't there be an Exit Block Statement in Smalltalk

9. Help! Deleted files I shouldn't have

10. Shouldn't I do so? [copyright law]

11. Shouldn't this be constant space?

12. shouldn't STOP?

 

 
Powered by phpBB® Forum Software