Last active
November 21, 2022 09:18
-
-
Save blaix/784cf15cbafb530243d489cd517d5edd to your computer and use it in GitHub Desktop.
How to understand `extend self` and `class << self` in ruby.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Everything in ruby is an object which is an instance of a class. Even classes | |
# themselves are objects that are instances of the class `Class`. | |
class Dog | |
end | |
puts Dog.class # => Class | |
# When you use the class keyword, you are opening a class to define instance | |
# methods for any instance of the class: | |
class Dog | |
def bark | |
puts "bark!" | |
end | |
end | |
dog1 = Dog.new | |
dog2 = Dog.new | |
dog1.bark # => "bark!" | |
dog2.bark # => "bark!" | |
# Every object in ruby also has an "invisible" singleton class (a metaclass): | |
puts Dog.singleton_class # => #<Class:Dog> | |
puts Dog.new.singleton_class # => #<Class:#<Dog:0x0123...>> | |
puts Dog.new.singleton_class # => #<Class:#<Dog:0x0456...>> | |
# When you say `def <some object>.<some method>`, you are defining an instance | |
# method on that object's singleton class: | |
def dog1.bark | |
puts "redefined bark!" | |
end | |
# and when you call a method in ruby, first it checks the object's singleton | |
# class, and THEN it checks object's class: | |
dog1.bark # => redefined bark! | |
dog2.bark # => bark! | |
# Since classes are objects, the same applies: | |
def Dog.eat | |
puts "yum!" | |
end | |
# We defined an instance method on the class object's singleton class (wee), | |
# and method lookup works the same way. There's nothing special about "class" | |
# methods in ruby! | |
Dog.eat # => "yum!" | |
# And there's always an implicit `self` object: | |
puts self # => main | |
# When you open a class, self changes to the class object: | |
class Dog | |
puts self # => Dog | |
end | |
# Which is why you can define "class methods" like this: | |
class Dog | |
# Remember `def <object>.<method>` syntax defines an instance method on the | |
# object's singleton class. Since here `self` is `Dog`, we define a class | |
# method by putting an instance method on the Dog's singleton class: | |
def self.eat_again | |
puts "yummy!" | |
end | |
end | |
Dog.eat_again # => yummy! | |
# Another way to define methods in ruby is with modules: | |
module Pisser | |
def piss | |
puts "pisss" | |
end | |
end | |
# Classes have a method called `include` which takes a module ands adds the | |
# module methods to the class as instance methods: | |
Dog.include(Pisser) | |
dog1.piss # => pisss | |
dog2.piss # => pisss | |
# Classes also have a method called `extend` which is described as adding "class | |
# methods" to the class, but to understand `extend self`, you need to understand | |
# that really `extend` is adding _instance_ methods to the class' _singleton_ | |
# class. Class methods are a ruby myth! | |
Dog.extend(Pisser) | |
Dog.piss # => pisss | |
# Also remember that using `class Whatever` is just opening the Whatever class, | |
# and `self` becomes the class, which means this is the same as calling | |
# `Dog.extend(Pisser)`: | |
class Dog | |
extend Pisser # => here the implicit `self` is `Dog` | |
end | |
# The same thing applies to modules.... | |
module AnotherPisser | |
extend Pisser | |
end | |
AnotherPisser.piss # => pisss | |
# Which means we can define modules to namespace "functions" (that are really | |
# just instance methods on the module's singleton class): | |
module Util | |
extend self # => the same as Util.extend(Util) | |
# Since Util extends itself, these methods will exist on Util's singleton | |
# class, making them callable directly on Util | |
def hello | |
puts "hello" | |
end | |
end | |
Util.hello # => hello | |
# Another way to define methods on an object's singleton class is with the | |
# `class << <object>` syntax. For example: | |
class << dog1 | |
def pet | |
puts "tail wag!" | |
end | |
end | |
dog1.pet # => tail wag! | |
# Which means... | |
class Dog | |
class << self | |
def zen | |
puts "ruby is radical!" | |
end | |
end | |
end | |
# Inside the Dog class, `self` is the Dog class object, so `class << self` opens | |
# the Dog class object's singleton class to define methods, and ruby checks the | |
# singleton class first on method lookups: | |
Dog.zen # => ruby is radical! | |
# The easy way to think about it is `extend self` is a convenient way to define | |
# "module methods" and `class << self` is a convenient way to define "class | |
# methods", but understanding the object model and method lookup shows there | |
# is nothing special going on. It's all just instance methods on objects. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment