Are There Unintended Consequences of Ruby's 'Begin ... End' Without 'Rescue' Used as a Code Block

Are there unintended consequences of Ruby's `begin ... end` without `rescue` used as a code block?

I use this sometimes if I want to assign something to a variable but I have to calculate the value I want to assign first. It makes the code a little bit more tidy this way. I think it's user preference. Basically you are saying: I am assigning something to foo, but in order to get the value I want I first need to do some things. It's particularly useful when doing memoization, so instead of

if @cache.nil?
do_something!
@cache = read_value
end

You can do

@cache ||= begin
do_something!
read_value
end

What you are taking advantage here is that the Ruby interpreter has a stack, and each expression will usually push something on the stack, or take something from the stack. Assignment just takes the last thing from the stack and assigns it (in this case the last line from begin/end). Many times knowing this (stack approach in Ruby) can be useful.

I don't think it violates least surprise though, I think it's user preference wheather you want to use it or not.

You can see that it doesn't do anything unexpected by looking at what bytecode instructions it generates in Ruby MRI 1.9:

 RubyVM::InstructionSequence::compile("c = begin; a = 5; 6; end").to_a

[:trace, 1],
[:trace, 1],
[:putobject, 5],
[:setlocal, 2],
[:trace, 1],
[:putobject, 6],
[:dup],
[:setlocal, 3],
[:leave]

Trace is just for stack traces, you can ignore that. Dup duplicates the last item on the stack. In this example the number of the local variable a is 2 and the number of the local variable c is 3 (hence putobject, 2 will assign to variable a, etc).
The only side-effect of this compared to a = 5; c = 6 is the dup instruction, which means the stack size of your method will be larger by 1 slot. But this is not particularly important because it only has any effect while the interpreter is inside this particular method, and memory for stack is pre-reserved anyway, so it only means the stack pointer will be decremented by 1 more than it would otherwise. So basically no change at all. With optimizations turned on even the dup will probably disappear.

What inspired Ruby's =begin .. =end comment block syntax?

yes, this is Perl

Perl uses

=begin
This is a comment line
it explains that the next line of code displays
a welcome message
=cut

have a look here: http://en.wikipedia.org/wiki/Comparison_of_programming_languages_(syntax)#Comments

Ruby multiline block without do end

There is a subtle difference between the two syntaxes. { } are higher precedence than do ... end. Thus, the following will pass bar and a block to method foo:

foo bar do ... end

while the following will pass a block to bar, and the result of that to foo:

foo bar { ... }

So your examples will act the same. However, if you left the parentheses off:

> 3.upto 9 { 
puts "Hi"
}
SyntaxError: compile error
(irb):82: syntax error, unexpected '{', expecting $end
3.upto 9 {
^
from (irb):82
from :0

> 3.upto 9 do
puts "Hi"
end
Hi
Hi
Hi
Hi
Hi
Hi
Hi
=> 3

So, { } are more likely to catch you up if you leave off parentheses in Ruby, which is fairly common; for this reason, and because Ruby conditionals and other control constructs all use end as a delimiter, people usually use do ... end for multi-line code blocks that come at the end of a statement.

However, { } is frequently use in places where do ... end would be cumbersome, for instance, if you are chaining several methods together which take blocks. This can allow you to write short, one line little blocks which can be used as part of a method chain.

> [1,2,3].sort{|x,y| y<=>x}.map{|x| x+1}
=> [4, 3, 2]

Here's an example to illustrate this difference:

def foo arg
if block_given?
puts "Block given to foo"
yield arg
else
puts "No block given to foo"
arg
end
end

def bar
if block_given?
puts "Block given to bar"
yield "Yielded from bar"
else
puts "No block given to bar"
end
"Returned from bar"
end

irb(main):077:0> foo bar { |arg| puts arg }
Block given to bar
Yielded from bar
No block given to foo
=> "Returned from bar"
irb(main):078:0> foo bar do |arg| puts arg end
No block given to bar
Block given to foo
Returned from bar
=> nil

ruby: using blocks as normal (inline) expressions?

You could use a lambda:

def foo
@foo ||= lambda do
# do complicated stuff
end.call
end

Note that it is important to call the lambda to actually execute the expression, ie

def foo
@foo ||= lambda do
# do complicated stuff
end
end

will return a lambda rather than your evaluated expression.

ruby when/how to use rescue/ensure?

In general exceptions should be used for exceptional events. Not regular application flow.

When catching exceptions always be specific and only rescue exceptions that you know what to do with.

begin 
pats "hello world"
rescue
nil
end

This example shows a serious flaw in the code in yout question - you created a black hole that swallows the NoMethodError that would have told us that there is a typo in the code. This makes debugging extremely difficult. This anti-pattern is known as Pokémon Exception Handling (Gotta catch em' all).

ensure just ensures that the code is run no matter if the code raised an exception or not. Its used to for example guarantee that the method closes a file handler that it has opened or rolls back a transaction. Its a really big hammer that should be used very sparingly.

HTTParty does not actually raise exceptions when the response code is a "error" code - because its not an exceptional event. Its a part of normal application flow when dealing with HTTP requests. Shit happens. HTTParty raises exceptions if you can't reach the server at all or you can't even reach the network. Those are exceptional events.

class GeolocationClient
include HTTParty
base_uri "https://www.googleapis.com/geolocation/v1"
format :json
attr_accessor :options

def initialize(api_key:)
@options = {
api_key: api_key
}
end

def geolocate
begin
response = self.class.get("/geolocate", options)
if response.successful?
response
else
logger.info("Geolocation API call was unsuccessful. Status code: #{response.code}")
handle_unsuccessful_request
end
rescue HTTParty::Error => e
logger.warn(e.message)
handle_unsuccessful_request
end
end

private

def handle_unsuccessful_request
{ "message" : "unable to find location" }
end
end

response.successful? tests if the response is in the 2xx "happy range". Use a switch statement if you want handle each code or range of codes separately.

Unexpected Result When Using Fibers

From the docs,

Upon yielding or termination the Fiber returns the value of the last executed expression

In addition to Fiber.yield calls, a fiber (like an ordinary function) returns the result of the final expression in the fiber.

The body of your fiber is this.

File.open(begin print "Enter filename: "; gets.chomp end).each{|l| Fiber.yield l}

Inside the .each, you yield each line of the file, which gets printed out as you've already observed. But then, when the fiber is done, it yields a final value, which is the result of File.open. And File.open returns the File object itself. So your sg.resume actually sees six results, not five.

"This is the first"
"This is the second"
"This is the third"
"This is the fourth"
"This is the fifth"
(the file object itself)

This actually points out a small issue in your program to begin with: You never close the file. You can do that either with File#close or by passing a block to File::open. In order to be completely safe, your fiber code should probably look like this.

sg = Fiber.new do
print "Enter filename: "
filename = gets.chomp
# By passing File.open a block, the file is closed after the block automatically.
File.open(filename) do |f|
f.each{|l| Fiber.yield l}
end
# Our fiber has to return something at the end, so let's just return nil
nil
end

begin
loop do
# Get the value. If it's nil, then we're done; we can break out of the loop.
# Otherwise, print it
value = sg.resume
break if value.nil?
puts value
end
rescue => err
puts "Error: #{err.message}"
end

Now, in addition to dealing with that pesky file handle, we have a way to detect when the fiber is done and we no longer get the "attempt to resume a terminated fiber" error.

What exactly does ensure raise ActiveRecord::Rollback do?

The code in ensure is always run even if the begin section raises an unrescued exception.

begin
puts "Hello"
ensure
puts "World!"
end
max@pop-os ~/p/playground> ruby runme.rb
Hello
World!
begin
puts "Hello"
raise "Oh noes!"
ensure
puts "World!"
end
max@pop-os ~/p/playground> ruby runme.rb
Hello
World!
Traceback (most recent call last):
runme.rb:3:in `<main>': Oh noes! (RuntimeError)

Note the order here - the ensure section is executed before the execution is halted by the exception.

Thats why its used for things like closing file handlers, connections or rolling back transactions which otherwise could leave the system unstable or tie up resources.

In that particular example they are hinting on how you could preview the state change caused by an action and then undo it by always performing a rollback - even if an unexpected exception occurs.

How to rescue an eval in Ruby?

Well, that was easy...

It turns out that, by default, the "rescue" statement does not catch all exceptions, but only those that are subclasses of StandardError. SyntaxError is a sibling/cousin of StandardError, not a subclass of it, so the rescue statement doesn't capture it unless explicitly told to.

To have the rescue block capture all exceptions, you need to change the code to the following:

#!/usr/bin/ruby

good_str = "(1+1)"
bad_str = "(1+1" # syntax error: missing closing paren

begin
puts eval(good_str)
puts eval(bad_str)
rescue Exception => exc
puts "RESCUED!"
end

Note the change in the "rescue" line, from "rescue => exc" to "rescue Exception => exc".

Now, when you run the code, you get the desired results:

2
RESCUED!


Related Topics



Leave a reply



Submit