1

Here, I create a local variable in class scope:

class MyClass
  x = 1
  puts x
end

It prints 1 even if I don't create any instances of MyClass.

I want to use x in some method:

class MyClass
  x = 1
  def method
    puts x
  end
end

m = MyClass.new
m.method

And I can't. Why? I get that class definition creates a scope, but why is it not accessible in the method? Isn't scope of the method inside the scope of the class?

I can imagine that this is related to creation of a class. Since any class is an object of Class, maybe the scope of MyClass is the scope of some Class method, and the way of coupling methods of MyClass to that instance makes their scope completely different.

It also seems to me that I can't just create a scope with {} (like in C) or something like do..end. Am I correct?

user513951
  • 12,445
  • 7
  • 65
  • 82
Amomum
  • 6,217
  • 8
  • 34
  • 62
  • 1
    It prints `1` because class declaration is just code that runs when the class is loaded. – Dave Newton Oct 16 '15 at 00:03
  • I have no concept of "class loading". Can you explain what does it mean? – Amomum Oct 16 '15 at 00:03
  • 1
    At runtime, after parsing, Ruby actually executes all of the code contained within the class definition. – user513951 Oct 16 '15 at 00:06
  • So just creating a class without any instances will lead to something actually executing in runtime (even allocating may be)? That is very not like C++. – Amomum Oct 16 '15 at 00:09
  • 1
    Correct. Ruby does not require any instances to be initialized to run the code within the class definition. It runs at the time that it is read, which may be before some other parts of the code in the codebase have been read. – user513951 Oct 16 '15 at 00:10

3 Answers3

4

Scope of a method is not inside the class. Each method has its own entirely new scope.

New scopes are created whenever you use the class, module, and def keywords. Using brackets, as in C, does not create a new scope, and in fact you cannot arbitrarily group lines of code using brackets. The brackets (or do...end) around a Ruby block create a block-level scope, where variables previously created in the surrounding scope are available, but variables created within the block scope do not escape into the surrounding scope afterward.

Instance methods share the scope of their instance variables with other instances methods. An instance variable defined in the scope of a class definition is available in class-level singleton methods, but not in instance methods of the class.

Illustration:

class Foo
  x = 1  # available only here
  @y = 2 # class-wide value

  def self.class_x
    @x # never set; nil value
  end

  def self.class_y
    @y # class-wide value
  end

  def initialize(z)
    x = 3  # available only here
    @z = z # value for this instance only
  end

  def instance_x
    @x # never set; nil
  end

  def instance_y
    @y # never set; nil
  end

  def instance_z
    @z # value for this instance only
  end
end

Foo.class_x # => nil
Foo.class_y # => 2

Foo.new(0).instance_x # => nil
Foo.new(0).instance_y # => nil

foo3 = Foo.new(3)
foo4 = Foo.new(4)

foo3.instance_z # => 3
foo4.instance_z # => 4

You can access class-level instance variables from within instances using the class-level getter. Continuing the example above:

class Foo
  def get_class_y
    self.class.class_y
  end
end

foo = Foo.new(0)
foo.get_class_y # => 2

There exists in Ruby the notion of a "class variable," which uses the @@ sigil. In practice, there is almost never a reasonable use case for this language construct. Typically the goal can be better achieved using a class-level instance variable, as shown here.

user513951
  • 12,445
  • 7
  • 65
  • 82
  • Thanks for you explanation! Can please you tell me, why is scope behaviour so different between class/method scope and method/let's-say-while-loop in that method? In the loop I'm able to access method local variable. I mean, is there any particular reason for this? – Amomum Oct 16 '15 at 00:02
  • The reason you're able to access method local variables inside a loop defined inside a method is so that you can use the power of closures. As for why methods define their own scope that is completely unlinked to the class-level scope... that's just a language implementation choice made by the creator of Ruby. – user513951 Oct 16 '15 at 00:05
  • The question is about **local** variables. I don't understand how your long examples/explanation about **instance** variables is relevant to this question. – sawa Oct 16 '15 at 00:10
  • 2
    @sawa, the still are very helpful for me! – Amomum Oct 16 '15 at 00:12
  • 1
    @sawa the question is about how to use variables in methods when those variables are defined elsewhere. A discussion of instance variables is very relevant. – user513951 Oct 16 '15 at 00:12
2

Here, I create a local variable in class scope:

class MyClass
  x = 1
  puts x
end

It prints 1 even if I don't create any instances of MyClass.

Correct. The class definition body is executed when it is read. It's just code like any other code, there is nothing special about class definition bodies.

Ask yourself: how would methods like attr_reader/attr_writer/attr_accessor, alias_method, public/protected/private work otherwise? Heck, how would def work otherwise if it didn't get executed when the class is defined? (After all, def is just an expression like any other expression!)

That's why you can do stuff like this:

class FileReader
  if operating_system == :windows
    def blah; end
  else
    def blubb; end
  end
end

I want to use x in some method:

class MyClass
  x = 1
  def method
    puts x
  end
end

m = MyClass.new
m.method

And I can't. Why? I get that class definition creates a scope, but why is it not accessible in the method? Isn't scope of the method inside the scope of the class?

No, it is not. There are 4 scopes in Ruby: script scope, module/class definition scope, method definition scope, and block/lambda scope. Only blocks/lambdas nest, all the others create new scopes.

I can imagine that this is related to creation of a class. Since any class is an object of Class, maybe the scope of MyClass is the scope of some Class method, and the way of coupling methods of MyClass to that instance makes their scope completely different.

Honestly, I don't fully understand what you are saying, but no, class definition scope is not method definition scope, class definition scope is class definition scope, and method definition scope is method definition scope.

It also seems to me that I can't just create a scope with {} (like in C) or something like do..end. Am I correct?

Like I said above: there are 4 scopes in Ruby. There is nothing like block scope in C. (The Ruby concept of "block" is something completely different than the C concept of "block.") The closest thing you can get is a JavaScript-inspired immediately-invoked lambda-literal, something like this:

foo = 1

-> {
  bar = 2
  foo + bar
}.()
# => 3

bar
# NameError

In general, that is not necessary in Ruby. In well-factored code, methods will be so small, that keeping track of local variables and their scopes and lifetimes is really not a big deal.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • What about block-local variables, e.g., the ones after the semicolon in the block parameters? – Dave Newton Oct 16 '15 at 00:50
  • I like to rather think of them as "not-captured variables". IOW, they are just normal local variables, plus they don't nest, i.e. they shadow an eventually existing outer variable of the same name. (I have a neat syntax extension in mind, which I wanted to suggest for years in the bugtracker, but haven't gotten around yet: `{|a, b; c, d, *| stuff }`, i.e. allowing a wildcard in the parameter list, basically meaning "this block is not a closure". This makes it semantically clear that you don't actually intend to capture any variables. It also allows compiler optimizations.) – Jörg W Mittag Oct 16 '15 at 00:54
  • That is soooo weird. Conditional def of a method made me wide-eyed. I always forget that ruby is a script language and no 'compile time' exist here. Thank you, now it makes more sense! – Amomum Oct 16 '15 at 07:28
  • "Honestly, I don't fully understand what you are saying, but no, class definition scope is not method definition scope, class definition scope is class definition scope, and method definition scope is method definition scope" I meant that I don't know how exactly class is created; if class is an object of Class class, maybe we can think that class MyClass ... end - is *somehow* glued into "empty" Class object.. or something. I read somewhere that class-level instance variables are actually in the scope of Class methods and thought in that direction. – Amomum Oct 16 '15 at 07:30
1

So just creating a class without any instances will lead to something actually executing in runtime (even allocating may be)? That is very not like C++. –

Check out this code:

Dog = Class.new do
  attr_accessor :name

  def initialize(name)
    @name = name
  end
end

If you execute that code, there won't be any output, but something still happened. For instance, a global variable named Dog was created, and it has a value. Here's the proof:

Dog = Class.new do
  attr_accessor :name

  def initialize(name)
    @name = name
  end
end

dog = Dog.new("Ralph")
puts dog.name

--output:--
Ralph

The assignment to the Dog constant above is equivalent to writing:

class Dog
  ...
  ...
end

And, in fact, ruby steps through each line inside the class definition and executes each line--unless the line of code is inside a def. The def is created but the code inside a def doesn't execute until the def is called.

A very common line you will see inside a class definition is:

attr_accessor :name

...which can be rewritten as:

attr_accessor(:name)

...which makes it obvious that it's a method call. Ruby executes that line--and calls the method--when you run a file containing the class definition. The attr_accessor method then dynamically creates and inserts a getter and a setter method into the class. At runtime. Yeah, this ain't C++ land anymore--welcome to NeverNever Land.

I get that class definition creates a scope, but why is it not accessible in the method?

Because that is the way Matz decided things should be: a def creates a new scope, blocking visibility of variables outside the def. However, there are ways to open up the scope gates, so to speak: blocks can see the variables defined in the surrounding scope. Check out define_method():

class MyClass
  x = 1

  define_method(:do_stuff) do
    puts x
  end

end

m = MyClass.new
m.do_stuff

--output:--
1

The block is everything between do...end. In ruby, a block is a closure, which means that when a block is created, it captures the variables in the surrounding scope, and carries those variables with it until the the block is executed. A block is like an anonymous function, which gets passed to a method as an argument.

Note that if you use the Class.new trick, you can open two scope gates:

x = 1

MyClass = Class.new do

  define_method(:do_stuff) do
    puts x
  end

end

m = MyClass.new
m.do_stuff

--output:--
1

Generally, ruby allows a programmer to do whatever they want, rules be damned.

7stud
  • 46,922
  • 14
  • 101
  • 127
  • 1
    Technically, the assignment to Dog in `Dog = Class.new do ... end` is not quite equivalent to `class Dog ... end`. A closer equivalent to the class declaration would be `Dog = Class.new; Dog.class_eval do ... end`. This is because `Class.new` temporarily creates an anonymous class without a name, and the name isn't assigned until after the block is evaluated and the assignment `Dog = ` occurs. Try these three constructs and you'll see the difference: `Dog = Class.new { p self.name }` vs `class Dog; p self.name; end` vs `Dog = Class.new; Dog.class_eval { p self.name }` – user513951 Oct 16 '15 at 00:55
  • Holy smokes! That gave me a horrifying thought that in Ruby I can even add a method to an object of some class, even after I created that object. And other objects of that class won't get that method AND I CAN oh my god. Let me guess, objects are actually some sort of hashmaps; method's name is a key and method "address" is value? – Amomum Oct 16 '15 at 07:38
  • @Amomum you are correct. Here is an illustration: `obj = Object.new; def obj.foo() :foo end; obj.foo #=> :foo; Object.new.foo #=> NoMethodError` – user513951 Oct 16 '15 at 19:52
  • @Amomum: *Let me guess, objects are actually some sort of hashmaps; method's name is a key and method "address" is value?* C++ also uses lookup tables for methods--the implementation of the lookup tables is unimportant. The real issue is: in which order are the lookup tables visited. In C++, inheritance creates multiple lookup tables. When a child object calls a method, first the child class is searched for the method, then it’s parent class. Inheritance creates a link between those two lookup tables. In ruby, every object has its own, unique parent-like class that it inherits methods from-- – 7stud Oct 16 '15 at 21:58
  • ...which is called its singleton class. In ruby, when an object calls a method, the first class that is searched for the method is the object’s singleton class. The next class that is searched is the class that created the object, i.e. the object's class. In other words, the singleton class of an object sits between the object and the object’s class in the inheritance chain. Then ruby's `include` statement or defining a class that inherits from another class adds classes to the top of an object's inheritance chain. The lookup paths can be a bit confusing to a beginner. Here is a thread... – 7stud Oct 16 '15 at 21:59
  • ...that contains some diagrams: http://stackoverflow.com/questions/23848667/ruby-method-lookup-path-for-an-object – 7stud Oct 16 '15 at 21:59
  • @7stud, waait, there is no run-time look-up tables in C++ for simple inheritance. That lookup is done by compiler (or linker) at compile time. Vtable for virtual functions is indeed a look-up table, but you can't just append some new method into it. – Amomum Oct 16 '15 at 22:37
  • @Amomum, Because of what you describe, the author of "Metaprogramming Ruby" describes C++ runtime as a ghost town, where ruby's runtime is described as a bustling market place, i.e. everything happens dynamically because methods can be added to a class at runtime etc., etc. – 7stud Oct 16 '15 at 22:56
  • @Amomum, By the way...another way to do what you want is to use a constant: `X=1`. In ruby, a constant is a variable that starts with a capital letter. – 7stud Oct 16 '15 at 23:02