ruby - How to return from inside eval? - ruby

I have a code which I need to use within eval. Sometimes I need to get out from the eval code, but my tries lead to errors.
E.g.:
# expected to see 1, 2 and 5; not 3 nor 4; and no errors
eval "puts 1; puts 2; return; puts 3; puts 4" # => Error: unexpected return
puts 5
I tried with return, end, exit, break, and I couldn't get success. exit doesn't raise errors, but then I don't get the 5.
(Note: I know that eval is evil, but in this case I need to use it.)

Thank you all, but I found a solution which fits best into my problem:
lambda do
eval "puts 1; puts 2; return; puts 3; puts 4"
end.call
puts 5
This way the intuitive return keyword can be used inside eval to get out from it successfully.
I didn't like the conditional-like solutions in this case because it would force me (or the user) to add an end at the end.
About using throw/catch or break, I consider the return keyword more intuitive.

eval'd code is just being run in this place. It's not a function or block. How would you do it without eval? Probably like this:
puts 1
puts 2
if(conditionFor3And4)
puts 3
puts 4
end

you Can't. You can return out of methods, and break out of blocks or loops, but not eval.
You could try a throw/catch block instead
eval "
should_stop = true
catch :stop do
puts 1
puts 2
throw :stop if should_stop
puts 3
end
"
or this:
should_stop = true
catch :stop do
eval "
puts 1
puts 2
throw :stop if should_stop
puts 3
"
end
or just do a conditional like Mchl said, since you probably want it to conditional stop, not just always, but throw catch will let you jump out of a block no matter how many levels down you are, which make it more robust, if you need to break out of a nested loop or something like that.

You could just use conditionals instead of early returns.

You could quite happily define a function and execute it last at the end of the script as follows:
def ev(s)
eval("def doStuff()\n" + s + "\nend\ndoStuff()")
end
ev("
1
return 2
3
")
#=> 2
Demo

Related

Why the global variable not works in .rb file but works in irb?

I was trying to add indentation based on how deep the block goes. I used a global variable to record the depth of block.
$depth = 0
def log(des, &block)
indentation = " " * $depth
$depth += 1
puts "#{indentation}Begginning the #{des} block"
puts "#{indentation}Finished #{des} and returned: #{block.call}"
$depth -= 1
end
log "outer block" do
log "second level block" do
log "third level block" do
"I am number 3"
end
"I am number 2"
end
"I am out most!"
end
In terminal I tried several times ruby file_name.rb, it showed no indentation, it even won't puts the global variable out. After then I copy the code to irb and it worked.
Why this happened?
What's the difference when running ruby code in between these two places?
I suspect you have a different definition of log() in your environment. Changing the name of your routine to something else (say 'mylog') will test this hypothesis.

Keeping a ruby script running in terminal so I can call different methods

I want to call a ruby script and keep it running while I call methods on it.
I have:
until (a = gets.chomp) =~ /(?:ex|qu)it/i
send(a)
end
This works very well, but I feel like it can't be the best practise?
Can someone reassure me / provide a better solution?
If you want a REPL, you could use IRB or PRY.
Otherwise you could write it yourself :
def handle_input(input)
raise StopIteration if input =~ /^(ex|qu)it$/i
result = eval(input)
puts("=> #{result}")
end
def repl(prompt)
print prompt
handle_input(gets.chomp!)
end
loop do
repl('>> ')
end
Example :
>> 2+3
=> 5
>> "test".size
=> 4
>> 3.times{|i| puts i}
0
1
2
=> 3
>> exit
Using eval usually isn't a good idea. But with your send, you cannot specify a receiver or any parameter.

Can I break a loop using a toplevel break within a Proc in Ruby?

Questions
Why does break within a proc jump out of three loops all the way to puts 8? It's pretty counter-intuitive.
Is there a way to make it break out of the innermost loop, that is, to puts 6?
Code
3.times do
puts "outer loop"
break_proc = proc { break }
puts 1
loop do
puts 2
loop do
puts 3
loop do
puts 4
break_proc.call
puts 5
end
puts 6
end
puts 7
end
puts 8
end
outer loop
1
2
3
4
8
outer loop
1
2
3
4
8
outer loop
1
2
3
4
8
TL;DR
The behavior you're seeing is a result of attempting to treat a Proc object like a snippet of code passed to Kernel#eval, or thinking that a toplevel break inside a Proc is the same as a bare break keyword inside a loop. An explanation for the behavior is provided, but the real solution is to avoid doing what you're doing.
Procs Carry Context
Why does break within a proc jump out of three loops all the way to puts 8?
This happens because a Proc object contains a Binding to the context in which it's created, and the break keyword is exiting the iterator block and returning to its calling context. Specifically, you're creating the Proc in the top-level loop here:
3.times do
puts "outer loop"
break_proc = proc { break }
One could be forgiven for thinking that Ruby's break just exits a loop wherever its called, but its behavior is more complex than that, especially when you're trying to do something odd like a toplevel break inside a Proc. Your use case for break is even covered in The Ruby Programming Language, where it says:
[A break] causes the block to return to its iterator and the iterator to return to the method that invoked it. Because procs work like blocks, we expect break to do the same thing in a proc. We can’t easily test this, however. When we create a proc with Proc.new, Proc.new is the iterator that break would return from. And by the time we can invoke the proc object, the iterator has already returned. So it never makes sense to have a top-level break statement in a proc created with Proc.new[.]
— David Flanagan and Yukihiro Matsumoto. The Ruby Programming Language (Kindle Locations 8185-8192). O'Reilly Media.
When you create deeply nested loops and then complicate that with objects that carry runtime bindings, the results aren't always what you expect. The behavior you're seeing is not a bug, although it may be a misfeature in some cases. You'd have to ask the language designers why it behaves this way if you want a reason for the implementation semantics rather an explanation for the behavior you're seeing.
Breaking Loops
Is there a way to make it break out of the innermost loop, that is, to puts 6?
Yes, but not with break inside a Proc. Replacing the Proc#call with an actual inline break statement does what you expect and is the "simplest thing that could possibly work," but you can also use throw and catch if you want to adjust your nesting level. For example:
3.times do
puts "outer loop"
break_proc = proc { throw :up }
puts 1
loop do
puts 2
loop do
puts 3
catch :up do
loop do
puts 4
break_proc.call
puts 5
end
end
puts 6
end
puts 7
end
puts 8
end
This will yield:
outer loop
1
2
3
4
6
3
4
6
3
4
6
and endlessly loop inside the third loop where you puts 3.
So, this will do what you're asking, but may or may not do what you want. If it helps, great! If not, you may want to ask a separate question with some real data and behavior if you want to find a more elegant data structure or decompose your task into a set of collaborating objects.
Because of context binding break escapes from the loop defined at the same level:
3.times do
puts 1
loop do
break_proc = proc {|b| break }
puts 2
loop do
puts 3
loop do
puts 4
break_proc.call
puts 5
end
puts 6
end
puts 7
raise 'break other loops'
end
puts 8
end
=>
1
2
3
4
7
1.rb:18:in `block (2 levels) in <main>': break other loops (RuntimeError)
Easiest way to break from your construction - return a boolean from the block indicating if loop should be terminated (... = proc{ true }/break if break_proc.call), or use throw:
3.times do
puts "outer loop"
break_proc = proc {|b| throw :breakit }
puts 1
loop do
puts 2
loop do
puts 3
catch :breakit do
loop do
puts 4
break_proc.call
puts 5
end
end
puts 6
raise 'break the other loops...'
end
puts 7
end
puts 8
end
If you want to break till 6 block you could do this
3.times do
puts "outer loop"
break_proc = proc { break }
puts 1
loop do
puts 2
loop do
puts 3
loop do
puts 4
break
puts 5
end
puts 6
end
puts 7
end
puts 8
end
Ruby Folks:
To use proc & lambda in Ruby, kindly consider the following info to use them error-free and use break or return with proper understanding:
Lambda and non-lambda semantics:
-------------------------------
Procs are coming in two flavors: lambda and non-lambda (regular procs). Differences are:
In lambdas, return and break means exit from this lambda;
In non-lambda procs, return means exit from embracing method (and will throw LocalJumpError if invoked outside the method);
In non-lambda procs, break means exit from the method for which the block is given. (and will throw LocalJumpError if invoked after the method returns);
In lambdas, arguments are treated in the same way as in methods: strict, with ArgumentError for mismatching argument number, and no additional argument processing;
Regular procs accept arguments more generously: missing arguments are filled with nil, single Array arguments are deconstructed if the proc has multiple arguments, and there is no error raised on extra arguments.
Ref: https://ruby-doc.org/core-3.0.2/Proc.html

Ruby recursion's behavior

In ruby, recursion is pretty straight forward. Everything before calling the method itself works as expected. However, strange thing happen after calling the method itself.
def AdditivePersistence(num, t = 0)
p "when begin, num is #{num}"
return t if num <= 9
t += 1
num = num.to_s.split("").inject(0) { |sum, x| sum += x.to_i }
p "after inject, num is #{num}"
AdditivePersistence(num, t)
p "-----------"
p "t = #{t}"
end
AdditivePersistence(56)
The result looks like this.
"when begin, num is 56"
"after inject, num is 11"
"when begin, num is 11"
"after inject, num is 2"
"when begin, num is 2"
"-----------"
"t = 2"
"-----------"
"t = 1"
This shows the code p '---------' after calling the method itself gets executed after the recursion is done for however many times the recursion happened.
My question is shouldn't p '---------' either be not executed (just like code after return), or get executed every time the method is called?
edited:
Added p "t = #{t}". This result makes me even more confused. Why does t = 2 the first time? t started from 0, and gets incremented in every recursion.
First of all, in ruby, you shouldn't use camel case, it would be better to rename the method to additive_persistence.
So you bother about
"-----------"
"-----------"
this output? It is normal behavior. The pointer is being returned to previous AdditivePersistence(num, t) call and execution continues to p "-----------"
The function is called a total of three times. The last call of the function results in an early return, but the first two calls run all the way to the end and print dashes before they return. This is normal, and if you just think a little bit more about how your program works you should see there is no problem.
I indented the lines of output from your program to indicate how deep the call stack is when the output was produced:
"when begin, num is 56"
"after inject, num is 11"
"when begin, num is 11"
"after inject, num is 2"
"when begin, num is 2"
"-----------"
"-----------"
No, of course program flow returns to where it should, and continues in the calling method. Recursive method invocation is just like any other method invocation. The flow of execution temporarily leaves the method and returns to the next statement. This is the same for every language. When a method calls itself, that doesn't behave any differently than when a method calls another method.
Consider:
def method_1
puts "a"
method_2
puts "b"
end
def method_2
end
Do you think this program should not print "b" just because it invokes method_2 and flow of control temporarily leaves method_1? No, obviously not. When method_2 finishes, program flow returns to method_1 and the next statement (puts "b") executes.
Perhaps you're expecting some kind of loop behavior, where the recusrive call jumps to the top, but that's not at all what happens:
def AdditivePersistence(num, t = 0)
while true
p "when begin, num is #{num}"
break if num <= 9
t += 1
num = num.to_s.split("").inject(0) { |sum, x| sum += x.to_i }
p "after inject, num is #{num}"
next
# Flow will never reach this point
p "-----------"
end
t
end
AdditivePersistence(56)
OK. So I found my answer. Thanks everyone for helping.
I found the answer from this short video that explains how recursive function works in C++, which I believe is a similar process. This is for future reference for anyone looking for this question (turned out searching for "recursive function" instead of being specific to ruby will lead me to lots of answer).
https://www.youtube.com/watch?v=k0bb7UYy0pY
Because of how the recursive call stack works, it will start unwinding after the call stack reaches the base case. That's why in my code, p "---------" is called twice, and why t = 2 first, then t = 1 in the output.
Correct me if I am not understanding this correctly.

Re-open a script in ruby

I would like to re-open my script at the end of its execution. I tried with load but it didn't work. Is what I want to do possible? If yes, what should I try?
This is the structure of my script:
usr_choice = gets.chomp.to_i
case usr_choice
when 1
# Do something
when 2
# Do something else
else
puts "Choice not recognised"
end
Basically I would like for example to go back to the user input (usr_choice) after going through the case statement. Without a GoTo.
So you want a loop? Well, use loop:
loop do
usr_choice = gets.chomp.to_i
case usr_choice
when 1
puts 'one'
when 2
puts 'two'
else
break # exit the loop (not the case construct as in other languages)
end
end
Example:
$ ruby filename.rb
1
one
2
two
1
one
kthxbye
$

Resources