Newbie scope question

Hi,

Thank you answers to my previous question(s) !

Why does this not work?

doc = File::open("my.txt")
    class Crap
              def junko
            @doc = doc
             @doc.each {|line| print line}
              end
    end
myobj=Crap.new
myobj.junko

The segment below does work - I just don't quite understand why the Crap
object can't 'see' the txt file in the above example...

doc = File::open("my.txt")
class Crap
  def junko(a)
  a.each {|line| print line}
  end
end
myobj=Crap.new
myobj.junko(doc)

Thanks in advance

Charles L. Snyder wrote:

Why does this not work?

Because the variable doc is local to the top-level scope and can't be seen inside the methods of Crap. This works:

$doc = File::open("my.txt")
class Crap
   def junko
     @doc = $doc
     @doc.each {|line| print line}
   end
end
myobj=Crap.new
myobj.junko
$doc.close

$doc is now a global. Of course, I would not recommend coding this way. For example your original code never closed the file (which is probably not that bad here because at exit open files will be closed.) In addition globals are bad in most cases.

Using File::open with a block, and passing in the file (like your second example) is better:

class Crap
   def junko(doc)
     @doc = doc
     @doc.each {|line| print line}
   end
end

File::open("my.txt") do |file|
   myobj=Crap.new
   myobj.junko(file)
end

That way the file is closed automatically when the block ends.

Ryan

Charles L. Snyder wrote:

Why does this not work?

doc = File::open("my.txt")
    class Crap
              def junko
            @doc = doc
             @doc.each {|line| print line}
              end
    end
myobj=Crap.new
myobj.junko

Please note that class Foo ... end does indeed open a new scope that is totally disconnected to the scope that surrounds it in the source file.

This basically means that you could workaround the issue with this:

File.open("my.txt") do |doc|
   Crap = Class.new do
     def junko
       doc.each { |line| print line }
     end
   end
end

In Ruby it is /not/ considered good style do conditionally define classes which is probably the reason why the source code you tried to use above is discouraged.

Ryan Leavengood wrote:

Because the variable doc is local to the top-level scope and can't be seen inside the methods of Crap.

That is interesting, though. In most cases, variables defined in the outer scope are available in the inner scope. This applies in the case of begin...end, blocks, and the if/while/etc control flow operators. It seems that the same rule does not apply to method and class definitions. Why? Is there anything else to which it doesn't apply?

Devin

Unlike some other languages, variables that are not in scope in Ruby
are completely inaccessible (unless you resort to certain tricks).
Class, Module and Method definitions introduce new scopes, so outside
variables will be entirely invisible. Conditionals and looping
statements (ie, if..else, while..end, case..when..end) do not
introduce new scopes, so they can access variables that are outside
them, and variables defined inside them will be accessible outside the
statement.

Blocks are a little more complicated; they don't really introduce a
new scope, but any variables defined only inside a block will not
escape the block.

foo = 23
class Bar # new scope! foo is not accessible
  baz = 42
  def qux # another new scope! no baz for you!
    puts baz # yoiks, that'll be a NameError
  end
end

And for blocks:

name = "Mark"
5.times{puts name} # works

5.times{|n| n+=1}
puts n # doesn't work, n stays in the block

n = nil # n exists before the block
5.times{|n| n+=1}
puts n # outputs: "6"

Hope this helps, and isn't too confusing :slight_smile: Except for blocks, it's
actually a simpler scheme than most other languages; no masking of
variable names in outer scopes.

cheers,
Mark

···

On 6/26/05, Devin Mullins <twifkak@comcast.net> wrote:

Ryan Leavengood wrote:

> Because the variable doc is local to the top-level scope and can't be
> seen inside the methods of Crap.

That is interesting, though. In most cases, variables defined in the
outer scope are available in the inner scope. This applies in the case
of begin...end, blocks, and the if/while/etc control flow operators. It
seems that the same rule does not apply to method and class definitions.
Why? Is there anything else to which it doesn't apply?

Wow, that was a simple and complete summary of scope in Ruby. That helps a lot. :slight_smile:

I had known that variables defined inside blocks didn't escape blocks. I didn't know that the same wasn't true for loops and conditionals. Essentially, scope is flat inside a class/module/method, with the odd exception of blocks.

This is good, except it makes blocks a little confusing

Example #1:
irb(main):001:0> a = nil
=> nil
irb(main):002:0> 5.times {|a| }
=> 5
irb(main):003:0> a
=> 4

Example #2:
irb(main):006:0> 5.times {
irb(main):007:1* a ||= 0
irb(main):008:1> a+=1
irb(main):009:1> print a, ' '
irb(main):010:1> }
1 1 1 1 1

Example #3:
irb(main):001:0> a = 0
=> 0
irb(main):002:0> 5.times {
irb(main):003:1* a ||= 0
irb(main):004:1> a+=1
irb(main):005:1> print a, ' '
irb(main):006:1> }
1 2 3 4 5

Not a huge issue. Just something to get used to.

Devin