Home > Software engineering >  Ruby’s secret trick to avoid “class methods” and keep its type system simple and elegant
Ruby’s secret trick to avoid “class methods” and keep its type system simple and elegant

Time:02-05

When I said that every Ruby object has a class, I lied. The truth is, every object has two classes: a “regular” class and a singleton class. An object’s singleton class is a nameless class whose only instance is that object. Every object has its very own singleton class, created automatically along with the object. Singleton classes inherit from their object’s regular class and are initially empty, but you can open them up and add methods to them, which can then be called on the lone object belonging to them. This is Ruby’s secret trick to avoid “class methods” and keep its type system simple and elegant

The above passage says that Ruby's secret trick to avoid class methods. I don't understand what the author means here. Where is Ruby stopping us to avoid class methods? for an example, look at the example shown below

class Raj
  def self.hi
    puts 'Hi'
  end
  def hello
    puts 'hello'
  end
end

object=Raj.new

object.hello
Raj.hi

As you can see in the preceding example, the class methods can still be created. yes? I understand that there are no true class methods in Ruby; instead, they are methods that are created for the Raj object. But, in any case, it's allowing me to create the method 'hi,' right? So, what does it mean when it says, 'This is Ruby's secret trick for avoiding "class methods" and keeping its type system simple and elegant'?

CodePudding user response:

I understand that there are no true class methods in Ruby; instead, they are methods that are created for the Raj object.

That's exactly it, though.

def self.hi
  puts 'Hi'
end

This is not a class method or static method. Those don't exist in Ruby. That's the whole point. Your class Raj defines an object of type Class. We can see its type with the #class function.

> Raj.class
 => Class 

We can also see its ancestors.

> Raj.class.ancestors
 => [Class, Module, Object, PP::ObjectMixin, Kernel, BasicObject] 

Class inherits from Module, since (for the most part) classes can do everything modules can. Module, in turn, inherits from Object, which has some modules of its own mixed in (PP:ObjectMixin is for pretty-printing, and Kernel gets you the nice helpers like puts) and eventually inherits from the root class BasicObject.

But this isn't the whole story, for Raj has its own class as well: its singleton class. We can see the full story by calling #singleton_class instead of #class.

> Raj.singleton_class.ancestors
 => 
[#<Class:Raj>,                                                                  
 #<Class:Object>,                                                               
 #<Class:BasicObject>,                                                          
 Class,                                                                         
 Module,                                                                        
 Object,                                                                        
 PP::ObjectMixin,                                                               
 Kernel,                                                                        
 BasicObject] 

Now there's a lot more going on. Raj is an instance of the singleton class of Raj, which inherits from the singleton class of Object, which in turn inherits from the singleton class of BasicObject, which inherits from Class and all of the stuff we saw before.

So when you define a method on the class Raj, you're defining it (as an instance method) on the singleton class #<Class:Raj>. And that class (currently) has one instance: Raj.


By the way, it's also useful to know that the term "singleton class" is a bit of a lie. As you can see, the class is very much not a singleton in general. For instance, the singleton class of Object, called #<Class:Object> above, actually has several instances: Object, Raj, String, and most Ruby classes. Personally, I prefer to call them eigenclasses for that reason, but "singleton class" is the official (and more well-known) term.

CodePudding user response:

The author is talking about the singleton class in this sentence, there is a really nice article to deep dive into ruby singleton class: https://medium.com/@leo_hetsch/demystifying-singleton-classes-in-ruby-caf3fa4c9d91

Here is a nice example extracted from this article:

class Vehicle
  def initialize(kms)
    @kms = kms
  end
  def drive
    puts "let's go!"
  end
end
  
car = Vehicle.new(1000)  
bus = Vehicle.new(3000)

def car.drive  
  print "I'm driving a car! "
  super
end

car.drive # "I'm driving a car! let's go!"  
bus.drive # "let's go!"

As you can see, here the #drive method has been overridden but only for the car object, the bus object is still using the #drive method defined in the Vehicle class.

This new method is defined on the singleton class (or shadow class) of the object, this is allowing you to define new methods on the fly on an object without polluting all the objects of this class.

CodePudding user response:

This means that Ruby doesn't implement class methods.

Indeed, the Ruby OBJECT Model, allows you to "emulate" the definition of class methods by defining instance methods on the Eigenclass:

class Greeting
  def self.hello
    'hello world!'
  end

  def self.eigenclass
    class << self
      self
    end
  end
end

Greeting.eigenclass      # => #<Class:Greeting>
Greeting.eigenclass.name # => nil

Greeting.singleton_methods                  # => [:hello, :eigenclass]
Greeting.eigenclass.instance_methods(false) # => [:hello, :eigenclass]

First, we define a Greeting.eigenclass method. This method returns self in the context of the eigenclass — by using the class << self ... end syntax. In this case, self contains an unnamed instance of the class Class (a.k.a an anonymous class). This anonymous class keeps track of the class to which it is attached — the Greeting class in our case.

Then, we can see that the singleton methods of the Greeting class are the instance methods of the Greeting eigenclass.

Feel free to have a look to this very detailed article to learn more about this concept.

CodePudding user response:

To illustrate @Sébastien P.'s answer:

dice = [1,2,3,4,5,6]  #an ordinary array instance

def dice.throw        #now it has an extra
  sample
end

p dice.throw #=>3
  •  Tags:  
  • ruby
  • Related