📜 ⬆️ ⬇️

Ruby, SmallTalk and class variables

The article is a translation of the Pat Shaughnessy note, originally sounded like Ruby, Smalltalk and Class Variables .

A couple of weeks ago an article by Ernie Miller (Ernie Miller) pushed me to the question: how do class variables work in Ruby? After a little research, it turned out that class variables can be a potential source of problems. In fact, John Nunemaker has already written the article “Ruby class and instance variables” , which dates from 2006 and remains relevant today. The fundamental problem of class variables is that they are divided between the class itself and all its subclasses — as John explained six years ago, and this can lead to confusion and strange code behavior.


')
For me, the main question is: “Why?”. Why does Ruby divide this value between all subclasses? Why is there a difference between class variables and instance variables? Where do these ideas come from? It turns out that the answer is simple: class variables work the same as they did in a much more ancient language called Smalltalk . Smalltalk was invented in the early 1970s by renowned computer scientist Alan Kay and a group of colleagues working at the Xerox PARC laboratory. With the invention of Smalltalk, Allan did not just invent a programming language; he came up with the whole concept of Object Oriented Programming (OOP) and first implemented it. Although not widely spoken now, Smalltalk has influenced many other object-oriented languages ​​that are widely spoken today - the most important of which are Object C and Ruby.

Today I want to take a look at how class variables work in Smalltalk, and compare versus how they work in Ruby. You will see that the idea of ​​class variables is not the only borrowing from Smalltalk. Most of the Ruby object model was also taken from Smalltalk.

Class variables in ruby


First, let's look at what class variables are and how they work in Ruby, using the example of John Nunmeker of 2006 : here is a simple Ruby class, Polygon , which contains a single class variable @@sides .

 class Polygon @@sides = 10 def self.sides @@sides end end puts Polygon.sides =>10 


This example is quite simple: @@sides is a variable to which any class or method of an instance of the Polygon class has access. In the example, sides is a class method that returns it.
At the conceptual level, inside, Ruby associates the @@sides variable with the same memory section that the Polygon class represents:
image

Trouble starts when you define a subclass; another example of john's:
 class Triangle < Polygon @@sides = 3 end puts Triangle.sides =>3 puts Polygon.sides =>3 


Notice that both class variables, Triangle.sides and Polygon.sides , have been changed to 3. In essence, Ruby creates a single variable inside itself that is shared by both classes:

image

I can write in more detail about the internal implementation of class variables in Ruby in the next post of my blog, but for the time being I will use the simplest diagrams. Instead, let's switch and learn a little more about Smalltalk ...

What is Smalltalk?


As I said above, Alan Kay invented Smalltalk at the same time as object-oriented programming when working at Xerox PARC in the early 1970s. This is the same lab that invented the personal computer, graphical user interface, and Ethernet and many many other things. Against this background, the invention of OOP seems the least important of the inventions!

In Smalltalk, Kay proposed terminology and ideas that go without saying today. Each value in Smalltalk, including language constructs such as blocks, is an object. A Smalltalk program consists of these objects and the way they interact; To call a specific function on Smalltalk, you 'send a message' to an object that implements this function. In Smalltalk, functions are called 'methods'. The object implements a series of methods. All this should sound very similar, of course.

From the very beginning, Kay's PLO concept incorporated the idea of ​​'class' objects. Object classes were described as a series of behaviors (methods), each instance of which could be invoked. Smalltalk also implemented the concept of polymorphism, which allows the developer to define 'subclasses', to inherit the behavior of their 'superclasses'. All these concepts that we often use today were invented by Kay and his colleagues 40 years ago.

Smalltalk, however, is more than a programming language - it is a whole graphical development environment. I consider Smalltalk a forerunner of Visual Studio and XCode, developed when Microsoft and Apple did not even exist, in a world where computers were used only for academic or government purposes. Another impressive goal of Alan Kay and the Smalltalk team, originally set, was to use their visual environment to educate children in schools. This is a truly amazing story!

To learn more about the history and origin of Smalltalk, I highly recommend reading The Early History of Smalltalk ( html , or pdf , or pdf but without diagrams ), a retrospective review Kay wrote later in the 1990s. This is a fascinating story about how Kay and his colleagues borrowed ideas from even the earlier past, but their combination was hard work, creative, where she could make a huge step forward with pure talent and make a revolution in the computer science world of their days, as well as ours.

Alan Kay created the first working version of Smalltalk in 1972 - in his own words, how it happened:
I expected the new Smalltalk to be a landmark language, and its development will take at least two years, but fate intervened in the plans. One fine day, during a typical male conversation in the hallway of PARC, Ted Kaehler, Dan Ingalls and I stood and talked about programming languages. It was time to discuss the power of the language and now they were interested in how to make the language super powerful. Drawing myself in front of them, I said that 'the most powerful language in the world' they can capture 'in pages with code'. Their cue was: "create or shut up." Ted went back to CMU, but Dan was still there and continued to push me further. The next two weeks I came to PARC at four o'clock in the morning and worked until eight, then Dan together with Henry Fuchs, John Shoch, and Steve Purcell began discussing morning work. I boasted in advance because the interpreter LISP (LISP) McCarthy (John McCarthy) was written in LISP-e. It was about that very “page with code”, and over time, when the power of the language grew, it became everything for functional languages. I was absolutely sure that I could do the same for object-oriented languages.

Here Kay refers to John McCarthy , who invented LISP ten years earlier. Kay took only eight mornings to complete the first version of Smalltalk:
The first few versions had flaws that were loudly criticized by the group. But by the eighth morning, or somewhere around that, the code was working ...

I would like to be as creative, versatile and productive as Alan Kay and his PARC colleagues were 40 years ago.

Class variables in smalltalk


To find out how class variables directly work in Smalltalk, I installed GNU Smalltalk, a command-line version of the language that is easy to download and run under the Linux Box. At the beginning, Smalltalk seemed to me very strange and unfriendly; its syntax is a bit strange at first glance. For example, to remember to the end every command, and also, when defining a method, you need to specify only a list of arguments ... without the name of the method! I suppose the first argument is the name of the method, or something like that. But after a couple of days I got used to a kind of syntax, and the language became more meaningful for me.

Here, the same Polygon class — Smalltalk code on the left, on Ruby on the right.
 Object subclass: Polygon [ Sides := 10. ] Polygon class extend [ sides [ ^Sides ] ] Polygon sides printNl. => 10 

 class Polygon @@sides = 10 def self.sides @@sides end end puts Polygon.sides => 10 



Further, a small explanation of what Smalltalk code does:
• Object subclass: Polygon - this means sending a subclass of a message to the Object class and passing the name Polygon. This will create a new class that is a subclass of the Object class. This is an analogy of the expression class Polygon <Object in Ruby. Of course, in Ruby, specifying Object as a superclass is optional.
• Sides := 10. - a Sides class variable is declared here, and a value is assigned to it. Ruby uses a different syntax: @@ sides.
• Polygon class extend - here the Polygon class extend expands; those. The Polygon class opens to give me the opportunity to add a class method. In Ruby, I use the construct: class Polygon; def self.sides
• printNl method displays the value in the console; this works in the same way as puts in Ruby, except that the printNl method is a method of the Sides object. Imagine just calling @@sides.puts in Ruby!

In addition to superficial differences in syntax, if you take a step back and think, you will discover how amazing Smalltalk and Ruby are! Both languages ​​not only share the concept of class variables, but also the writing of the Polygon class, the declaration of a class variable and the output of the values ​​in them in the same way. In fact, you can think of Ruby as a new version of Smalltalk with a simplified, and more convenient syntax!

As I said above, Smalltalk shares class variables between subclasses, in the same way Ruby does. Here is an example of how I declare the Triangle subclass in Smalltalk and Ruby.
 Polygon subclass: Triangle [ ] Triangle class extend [ set_sides: num [ Sides := num ] ] Polygon sides printNl. => 10 

 class Triangle < Polygon def self.sides=(num) @@sides = num end end puts Triangle.sides => 10 


Here I declare the Triangle subclass and its method to set the value of its class variable. Now, let's try changing its value from a subclass.
 Triangle set_sides: 3. Triangle sides printNl. => 3 

 Triangle.sides = 3 puts Triangle.sides => 3 


No surprises; By calling the class method set_slides ( slides= in Ruby), I can update the value. But since Triangle and Polygon share a class variable, this also changes the Polygon class:
 Polygon sides printNl. => 3 

 puts Polygon.sides => 3 


Languages ​​differ in one: Smalltalk allows you to create separate class variables for each subclass, if you want. If you continue to declare a class and its access method in the parent class and its successor, they will become separate variables. At least in GNU Smalltalk, which I use:
 Object subclass: Polygon [ Sides := 10. ] Polygon class extend [ sides [ ^Sides ] ] Polygon subclass: Triangle [ Sides := 3. ] Triangle class extend [ sides [ ^Sides ] ] Polygon sides printNl. >= 10 Triangle sides printNl. >= 3 

This is not so in Ruby. As we saw above, @@sides always refers to the same value.

Class instance variables


In Ruby, if you want to have separate values ​​for each class, then you must use class instance variables instead of class variables. What does it mean? Let's take a look at another example of John Nunmeker:
 class Polygon def self.sides @sides end @sides = 8 end puts Polygon.sides => 8 

Now, when I use @sides instead of @@sides , Ruby creates a class instance variable instead of a class variable:

Conceptually, there is no difference, until I create a subclass of Triangle again:
 class Triangle < Polygon @sides = 3 end 

The class now owns its own copy of the @sides value:

Now let's try the same in Smalltalk. In Smalltalk, to declare an instance variable, you call the instanceVariableNames method in a class:
 Object subclass: Polygon [ ] Polygon instanceVariableNames: 'Sides '! Polygon extend [ sides [ ^Sides ] ] 

 class Polygon def sides @sides end end 


In this example, I created a new Polygon class, a subclass of the Object class. Then, I send an instanceVariableNames message to this new class, telling Smalltalk to create a new instance variable called Sides . Finally, I reopen the Polygon class and add the sides method to it. Nearby I wrote the corresponding code on Ruby.

Thus, Sides and @sides are instance variables of the Polygon class. To create a class variable in Smalltalk, you need to send a class message to Polygon before calling instanceVariableNames or extend , as shown below:
 Object subclass: Polygon [ ] Polygon class instanceVariableNames: 'Sides '! Polygon class extend [ sides [ ^Sides ] ] 

 class Polygon def self.sides @sides end end 


Notice again the two different code snippets (Ruby and Smalltalk) that the same commands execute in two different ways. In Smalltalk, you write Polygon class extend [ sides ..., while Ruby: class Polygon; def self.sides class Polygon; def self.sides . Ruby seems to me a shorter form of Smalltalk.

Metaclasses in Smalltalk and Ruby


Let's take another look at the lines of code that I used to create class instance variables in Smalltalk:
 Polygon instanceVariableNames: 'Sides '! 

Translated from programming language into Russian, it means:
• Take the Polygon class
• Helm him a message called instanceVariableNames
• and pass the string Sides , as a parameter.

This is an example of how to create instance variables in Smalltalk. I will create instances of the Polygon class, and they will all have an instance variable of the Sides class. In other words, to create a class instance variable for each polygon instance created, I call a method on the Polygon class.
As I explained above, to create a class instance variable in Smalltalk, you must use the 'class' keyword. For example:
 Polygon class instanceVariableNames: 'Sides '! 

This code literally means the following: calling the instanceVariableNames method on the class 'Polygon' class. In a similar way, all instances of the Polygon class will contain an instance variable of the class. But what does it mean: 'class class Polygon' in Smalltalk? After spending a few moments in the GNU Smalltalk REPL, we find:
 $ gst GNU Smalltalk ready st> Polygon printNl. => Polygon st> Polygon class printNl. => Polygon class 

In the example, first, I display an object of the Polygon class. Then, I try to find out what the class of the Polygon class is. And this is the 'Polygon class'. But what kind of object is this? Let's call the class on it:
 st> Polygon class class printNl. => Metaclass 

Ah ... that's it. Class class is a metaclass. Above, when I called instanceVariableNames to create a class instance variable, I actually used the Polygon metaclass, an instance of the Metaclass class.

The diagram below shows how all these classes relate to Smalltalk:

Now, it should not come as a surprise that Ruby uses the same model. Here’s how classes are organized inside Ruby:

In Ruby, whenever you create a class, Ruby creates an appropriate metaclass inside. Unlike Smalltalk, Ruby does not use this for class instance variables, but only for tracking class methods. Also, Ruby does not have a Metaclass class, but instead of all metaclasses, it creates instances of the Class class.

In Ruby, the metaclass is hidden, being a 'mysterious concept'. Ruby silently creates it, without telling you about it, and does not use it directly. In Smalltalk, however, metaclasses are hidden and play a huge role in the language. Creating a class instance variable, as shown above, is just one example of using metaclasses in Smalltalk. Another good example is how you add class methods by invoking extend .

When you request a class class in Ruby, you just get a Class. Ruby tells you nothing about metaclasses:
 $ irb > class Polygon; end > Polygon.class Class 

To see Ruby's metaclass, try the following trick:
 $ irb > class Polygon > def self.metaclass > class << self > self > end > end > end => nil > Polygon.metaclass => #<Class:Polygon> 

“# <Class: Polygon>” is the metaclass of the Polygon class. This syntax means a “Class instance for the Polygon class” or a metaclass for the Polygon.

Source: https://habr.com/ru/post/167457/


All Articles