3

I am transitioning to Ruby from Java and trying to understand Ruby scoping.

It seems class variables behave the same as static variables in Java. Is this interpretation correct? If so, are Ruby global variables then equivalent to public static variables in Java?

ab11
  • 19,770
  • 42
  • 120
  • 207
  • I'm not familiar with Java but found [this](http://stackoverflow.com/a/2086837/4001311) answer, hope it helps! – Shmully Wolfson Aug 31 '16 at 17:35
  • 1
    A word of advice: looking for similarities between a language you know and a language you are learning can be a dead end, often resulting in a non-idiomatic and ugly code in a new language. I would rather focus on particular problems and how you address them in different languages. Pick one problem that is usually solved with static variables in Java, then ask what's the right way to solve it in Ruby. The answer might be class variable, but more often would be something apparently unrelated. – Mladen Jablanović Aug 31 '16 at 18:48
  • No, but I had the same mistaken intuition so I feel you. – Casey Sep 01 '16 at 02:37
  • @MladenJablanović I think it's pretty much inevitable that you are going to carry your previous intuitions forward into a new language and while that can lead you astray it also saves you a lot of time in most cases. – Casey Sep 01 '16 at 02:39
  • @Casey: Agreed, I'm just saying, when asking a question (on SO or elsewhere), better start with "how do I _solve this problem_ here" than "how do I _use language A feature_ here". A kind of XY problem, I guess. – Mladen Jablanović Sep 01 '16 at 08:03

3 Answers3

3

I'm not familiar with java, but I can explain in Ruby terms.

In Ruby, class variables have @@ before the variable name. It doesn't behave as you think it should. Once a class variable is created, it is shared among all the subclasses. It is not limited to the class you create it in.

As for global variables, it has $ before the variable name. It does exactly what it sounds like it does, it is global and can be access anywhere.

Both of the variables are not recommended in Ruby. You can do what you need using instance variables, which has @ before the name. It is specific to the object. If you declare it in one object, it will not show up in another.

I hope this clears this up.

davidhu
  • 9,523
  • 6
  • 32
  • 53
  • I would also mention that ivars could also be set on a _class object_. – Mladen Jablanović Sep 01 '16 at 08:10
  • Global variables are indeed to be avoided, but class variables are a perfectly appropriate solution when state is to be shared among instances of a class and its subclasses. – ComDubh Sep 19 '18 at 09:40
2

There's a lot of similarity between Ruby and Java by virtue of them being object-oriented, but their family tree is different. Ruby leans very heavily on Smalltalk while Java inherits from the C++ school of thinking.

The difference here is that Ruby's concept of public/private/protected is a lot weaker, they're more suggestions than rules, and things like static methods or constants are more of a pattern than a construct in the language.

Global variables are frowned on quite heavily, they can cause chaos if used liberally. The Ruby way is to namespace things:

$ugly_global = 0  # Not recommended, could conflict with other code
                  # Ownership of this variable isn't made clear.

$ugly_global += 1 # Works, but again, it's without context.

module UglyCounter    # Defines a module/namespace to live in
  def self.current    # Defines a clear interface to this value
    @counter ||= 0    # Initializes a local instance variable
  end

  def self.current=(v)   # Allow modification of this value
    @counter = v.to_i    # A chance to perform any casting/cleaning
  end
end

UglyCounter.current += 1   # Modifies the state of a variable, but
                           # the context is made clear.

Even a thin layer like this module gives you the ability to intercept read/write operations from this variable and alter the behaviour. Maybe you want to default to a particular value or convert values into a normalized form. With a bare global you have to repeat this code everywhere. Here you can consolidate it.

Class variables are a whole different thing. They're also best avoided because sharing data between the class and instances of this class can be messy. They're two different contexts and that separation should be respected.

class MessyClass
  @@shared = 0

  def counter
    @@shared
  end

  def counter=(v)
    @@shared = v
  end
end

This is a pretty rough take on how to use a shared class-level instance variable. The problem here is each instance is directly modifying it, bypassing the class context, which means the class is helpless. This is fundamentally rude, the instance is over-extending its authority. A better approach is this:

class CleanerClass
  def self.counter
    @counter ||= 0
  end

  def self.counter=(v)
    @counter = v.to_i
  end

  # These are reduced to simple bridge methods, nothing more. Because
  # they simply forward calls there's no breach of authority.
  def counter
    self.class.counter
  end

  def counter=(v)
    self.class.counter = v
  end
end

In many languages a static class method becomes available in the scope of an instance automatically, but this is not the case in Ruby. You must write bridge/proxy/delegate methods, the terminology here varying depending on what you're used to.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • It's also worth mentioning the difference in the value of `self`. – Kedar Mhaswade Aug 31 '16 at 18:45
  • Good point. `self` is always the context you're in unless you go out of your way to rebind, so there's that too. – tadman Aug 31 '16 at 18:54
  • 1
    AFAIK the big issue with class variables isn't so much having information shared across instances (after all, the class method thing doesn't solve it) but that [they can bowl over each other in unexpected ways](http://archive.oreilly.com/pub/post/nubygems_dont_use_class_variab_1.html). – Casey Sep 01 '16 at 02:55
2

One important difference is the way class methods and instance variables interact in Ruby in a way that static methods and instance variables cannot in Java.

tadman's answer contains this code:

class CleanerClass
  def self.counter
    @counter ||= 0
  end

  def self.counter=(v)
    @counter = v.to_i
  end
end

To the Java (and C#) programmers of the world, this code looks like it shouldn't work. After all, in that model the idea of accessing an instance variable from a static method is incoherent (and won't even compile). The whole point is that you haven't got an instance!

However, in Ruby, every class definition itself an instance of the Class type. The reference to @counter actually creates an instance variable on the Class object defining CleanerClass. Changing it to @@counter, as it might seem to like like you should, will pollute other classes besides just the one in which you declare it.

Casey
  • 3,307
  • 1
  • 26
  • 41