Looking for simple Ruby scoping docs

I always liked Ruby but one simple thing kept me from really digging into Ruby… Scoping.

I’m missing something fundamental about Ruby’s scoping. I know the code below has a scoping problem because I get an error.

#! /usr/bin/env ruby
#encoding: utf-8

arr = [1, 2, 3,]

def displayArr
  arr.each{|e| puts e}
end

displayArr

But I fail to see why this fixes the problem

#! /usr/bin/env ruby
#encoding: utf-8

arr = [1, 2, 3,]

def displayArr(a)
  a.each{|e| puts e}
end

displayArr(arr)

Is there any docs which cover Ruby scoping? Something that explains why its done this way.

Note: I prefer to read something that explains why its done this way over something that demonstrates the scoping rules.

Ruby starts a new scope whenever you start a new Class, Module or Method definition (these are called “scope gates”). That is why in your first case ‘arr’ is not visible within ‘displayArr’. In the second case you are passing a reference to the value so it works.

I read about this in the book Metaprogramming Ruby by Paolo Perrotta, which discusses how to “flatten the scope”.

If writing a simple script, you could use a global variable:

$arr = [1, 2, 3,]

def displayArr
  $arr.each{|e| puts e}
end

displayArr

although I much prefer the second solution, passing in a value to a method.

I think what bothers me the most(in my posted example) is that I expected the scopes to nest in my posted example. I expected arr to be available in displayArr.

To make arr available to displayArr:

#! /usr/bin/env ruby
#encoding: utf-8

arr = [1, 2, 3,]

define_method(:displayArr) do
	arr.each{|e| puts e}
end

displayArr

But this also defines the method under Kernel if defined like this.

This define_method() or define_singleton_method(), etc, are very helpful because they work as block, but def don’t. So if you need to access variables outside the scope, either use instance variables, global variables, or use define_method()

Can someone explain why its done this way or at least show me another language that does it this way?

Crystal also does it that way :rofl::rofl:

And as said above by me and pcl, you have options, you can use global variable or instance variable to do that. It’s not like you can’t absolutely do that.

If you think that’s confusing try JS:


Or maybe try perl:

$ perl -e "print 'a' x 5"
aaaaa

$ perl -e "print 'b' x 5"
bbbbb

$ perl -e "print 5 x 5"
55555

Which language use x to multiply string?


Everything is language specific, and you shouldn’t compare one to another.

JS also has let, var, const, let is more like defining local variable like Ruby, but it pollutes nested scopes. Ruby doesn’t do that. What other language use let and var to define variables? It’s just so confusing, Most of the times JS programs will work without those. People use ; in JS, even though it has Automatic Semicolon Insertion. No other ASI language ask you to use ;, in fact it’s a bad practice in Ruby or Python or in most shells.

So, if another language doesn’t have block concept like Ruby, doesn’t mean Ruby has to eliminate that. It’s been there like this forever. If you think Ruby’s syntax is confusing, try using JS for your console apps!

I think you mistook me. This isn’t a complaint about Ruby. If anything, its a complaint about me and understanding local variables in Ruby.

I just find it remarkable that I can’t understand why locals behave this way in Ruby. There must be a reason it was designed this way. They must have been designed this way for a reason and I can’t figure out what that reason is.

I’m odd this way. If I can’t understand why something behaves the way it does… I can’t understand it and use it with confidence.

There must be a reason it was designed this way. They must have been designed this way for a reason

Actually, I think the other way. Why should local variables be visible from within a method? A method definition is a “clean room” - I should be able to write a method definition and use that method anywhere without worrying about the context of local variables. Even in small scripts, I wouldn’t intermingle variable and method definitions in the way you did - it just seems unnatural to me. e.g. in Java, there is no option to declare local variables and methods in the mixed way that Ruby allows.

Allowing local variables to be visible inside methods opens up several complications:

  • e.g. say you gave me a method definition, and I called it. Should I have to worry about my local variable names to be able to use your method? e.g. if your method uses internally the variable i, then suddenly I cannot use the variable i because you did? How would I know that?

  • Or perhaps methods only “see” the local variables where they are defined, not where they are called? So I have to be careful where I copy-paste that method?

There’s a separate concept, the “closure”, which is a function which includes its local environment (local variables) at the point where it is defined. Closures are defined “on the fly”, when you need them in specific cases. You can see an example of a closure with do-end blocks, e.g.:

sum = 0
[1,2,3,4].each do |number|
  sum += number
end
puts sum

The do-end closure includes the local environment so it can use sum. The do-end defines a (nameless) function that gets passed to tne each method as an argument.

As @SouravGoswami has said, different languages offer different ways of doing things and different ways of thinking about problems. Which language are you most familiar with? I suspect that language has left you thinking of programs in “one way”.

To my way of thinking, Ruby’s method definitions are perfectly natural. You get a “clean room” within the method definition, but if you really, really want to work with the local environment within your method, you have closures and the scope-flattening define_method etc techniques.

1 Like

Rust works this way. You can nest a function inside another function, but it cannot see the local variables, e.g.

fn main() {
    let x = 3;

    fn inner() {
        println!("X is {}", x)
    }

    inner();
}

gives error:

error[E0434]: can't capture dynamic environment in a fn item
 --> fns.rs:8:29
  |
8 |         println!("X is {}", x)
  |                             ^
  |
  = help: use the `|| { ... }` closure form instead

error: aborting due to previous error

Note it recommends using a ‘closure’ if we want to see the local scope.

In Go, you can only use a ‘nameless’ function definition, i.e. a closure, within another function:

package main

import "fmt"

func main() {
  x := 3

  inner := func() { fmt.Printf("X is %d\n", x) }

  inner()
}

C supports shadowing of local variables, and it allows you to use a local variable in a local function definition:

#include<stdio.h>

int main(int argc, char* argv) {
  int x = 3;

  int inner() {
    printf("X is %d\n", x);
  }

  inner();
}
1 Like