Lazy evaluation of the string "# {}" in ruby

I started typing statements in all my code. In order not to clutter up the output, I did something like:

dputs LEVEL, "string" 

where LEVEL is 0 for errors, 1 for important .. 5 for detailed and compared with DEBUG_LEVEL . Now my problem is that in a statement like:

 dputs 5, "#{big_class.inspect}" 

the line is always evaluated, also if I set DEBUG_LEVEL to 1. And this evaluation can take a lot of time. My favorite solution would be something like:

 dputs 5, '#{big_class.inspect}' 

and then, if desired, evaluate the line. But I am unable to get a string in a form that I can evaluate. So the only thing I can come up with is:

 dputs( 5 ){ "#{big_class.inspect}" } 

but it just looks ugly. So how can I evaluate the string '# {}'?

+4
source share
4 answers

I don’t think you can evade ugliness. Interpolation occurs before dputs is called, unless you put it inside a block that defers it until dputs evaluates it. I don't know where dputs comes from, so I'm not sure what its semantics are, but I assume that this block will give you the lazy grade you want. Not really, but he does the job.

+1
source

You can do this using dputs use sprintf (via % ), so it can decide not to build the interpolated string if it doesn't know that it is going to print it:

 def dputs(level, format_str, *vars) puts(format_str % vars) if level <= LEVEL end LEVEL = 5 name = 'Andrew' dputs 5, 'hello %s', name #=> hello Andrew 

Or, as you think, you can pass a block that deferred interpolation until the actual execution of the block:

 def dputs(level, &string) raise ArgumentError.new('block required') unless block_given? puts string.call if level <= LEVEL end 
+6
source

I think this doesn't make any difference, but I just came up with:

 2.3.1 :001 > s = '#{a}' => "\#{a}" 2.3.1 :002 > a = 1 => 1 2.3.1 :003 > instance_eval s.inspect.gsub('\\', '') => "1" 2.3.1 :004 > s = 'Hello #{a} and #{a+1}!' => "Hello \#{a} and \#{a+1}!" 2.3.1 :005 > instance_eval s.inspect.gsub('\\', '') => "Hello 1 and 2!" 

Do not use this in the manufacturing process :)

+1
source

OK, obviously, I was too lazy. I thought there should be a cleaner way to do this, Ruby is the best programming language and all;) To evaluate a string like

 a = '#{1+1} some text #{big_class.inspect}' 

only when necessary, I did not find a better way than to go through the line, and eval all "# {}" collided:

 str = "" "#{b}\#{}".scan( /(.*?)(#\{[^\}]*\})/ ){ str += $1 str += eval( $2[2..-2] ).to_s } 

If you are not clear, you can get rid of the temporary variable str :

 "#{b}\#{}".scan( /(.*?)(#\{[^\}]*\})/ ).collect{|c| c[0] + eval( c[1][2..-2] ).to_s }.join 

The String.scan method goes through each block "# {}", as there can be several evaluating it ( 2 ..- 2 cuts out "# {" and "}") and place it along with the rest of the string.

For a rectangle in a line that does not end with a '# {}' block, an empty block is added to be sure.

But well, after several years working in Ruby, it still seems awkward and C-ish. Maybe it's time to learn a new language!

0
source

Source: https://habr.com/ru/post/1440986/


All Articles