Ruby blocks with parameters
I'm reading 开发者_Python百科the Eloquent Ruby book (awesome book so far) and don't understand one section that talks about blocs, params, and scope. Here's the code...
class SomeApplication
# Rest of the class omitted...
def do_something
with_logging('load', nil) { @doc = Document.load( 'book' ) }
# Do something with the document...
with_logging('save', @doc) { |the_object| the_object.save }
end
def with_logging(description, the_object)
begin
@logger.debug( "Starting #{description}" )
yield( the_object )
@logger.debug( "Completed #{description}" )
rescue
@logger.error( "#{description} failed!!")
raise
end
end
end
The book says the code is more complex than it needs to be because @doc is automatically visible inside the code block. So there's no need to pass it down as an argument.
I don't understand which param he's talking about, the @doc or |the_object|
. What would this code look like after changing it to remove the unnecessary complexity?
Or does it mean that the @doc that was created in with_logging('load', nil)
is what is still visible? Even if that's the case I'm not sure how the with_logging
method at the bottom would access it.
@doc
is an instance variable. It is defined within the scope of an object (in this case, the instance of the class SomeApplication
), and is typically used to store a value that "belongs" to the instance. Instance variables are always available to the instance methods of an object. They are not available outside the object, unless you make them into attributes.
Your confusion re: the example probably stems from the roundabout way in which the author is passing the value from the method do_something
to the method with_logging
. When with_logging
is called, it receives two arguments, 'save'
and @doc
. In with_logging
the local variable description
is set to 'save'
, and the local variable the_object
is set to @doc
. Then yield
is called with the argument the_object
, This passes the_object
to the code block defined in the second call to with_logging
, which you can think of as sort of anonymous function. But of course, as the author points out, @doc
was already set in the first call to with_logging
, so it is unnecessary to pass it around as an argument. He could have written the second function call as:
with_logging('save') { @doc.save }
and the first as:
with_logging('load') { @doc = Document.load('book') }
then call yield
with no arguments, and the effect would be the same.
Blocks (and lambdas/procs) are closures. This means that they can access any variables in the environment they are created in. @doc
is an instance variable, so it is always in scope in the context of the class. @doc
is the value that is bound to the_object
, which is unnecessary. The revised code would look like:
class SomeApplication
# Rest of the class omitted...
def do_something
with_logging('load') { @doc = Document.load( 'book' ) }
# Do something with the document...
with_logging('save') { @doc.save }
end
def with_logging(description)
begin
@logger.debug( "Starting #{description}" )
yield
@logger.debug( "Completed #{description}" )
rescue
@logger.error( "#{description} failed!!")
raise
end
end
end
It's an instance variable (indicated by the @) so is available throughout the class, in any method.
精彩评论