Last active
December 8, 2016 17:12
-
-
Save jrust/5963217 to your computer and use it in GitHub Desktop.
An interactive tutorial on the self, singleton class, and method hierarchy 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
# Resources: | |
# Method lookup diagram: http://phrogz.net/RubyLibs/RubyMethodLookupFlow.pdf | |
# Seeing metaclasses clearly: http://ruby-metaprogramming.rubylearning.com/html/seeingMetaclassesClearly.html | |
require 'rubygems' | |
require 'pry-debugger' | |
require 'awesome_print' | |
# Objects hold data, classes hold methods | |
# Just take a look at the source for Class | |
# struct RClass { | |
# struct RBasic basic; | |
# struct st_table *iv_tbl; | |
# struct st_table *m_tbl; | |
# VALUE super; | |
# }; | |
# It has a table of methods and a pointer to the superclass | |
# | |
# And an object just as a table of instance vars: | |
# struct RObject { | |
# struct RBasic basic; | |
# struct st_table *iv_tbl; | |
# }; | |
class Dog | |
attr_accessor :name | |
def initialize(name) | |
self.name = name | |
end | |
def who_is_self | |
self | |
end | |
end | |
fido = Dog.new 'fido' | |
ap "-> fido.inspect # So our data is on the object, but where's that name accessor?" | |
ap fido.inspect | |
ap "-> fido.singleton_class # An instance of Class with fido as its argument. Also known as metaclass or eigenclass." | |
ap fido.singleton_class | |
ap "-> fido.singleton_class.instance_methods(false) # nope, nothing here" | |
ap fido.singleton_class.instance_methods(false) | |
ap "-> fido.singleton_class.ancestors # The singleton class inherits all the way up to BasicObject" | |
ap fido.singleton_class.ancestors | |
ap "-> fido.singleton_class.superclass # And it's parent is Dog" | |
ap fido.singleton_class.superclass | |
ap "-> fido.singleton_class.superclass.class # And as we'd expect, it's a Class" | |
ap fido.singleton_class.superclass.class | |
ap "-> fido.singleton_class.superclass.instance_methods(false) # And as we learned above, only Class can hold methods" | |
ap fido.singleton_class.superclass.instance_methods(false) | |
ap "-> fido.who_is_self # And who is the self for this object? Just the receiver, our dog Object" | |
ap fido.who_is_self | |
binding.pry | |
# So, what's the point of fido.singleton_class if it's empty? | |
# so that this instance of dog can have methods added only to it. | |
# Of course there are lots of ways to achieve this: | |
# Use the "open it up" syntax | |
class << fido | |
def bark; "I'm #{@name}" end | |
end | |
# Define it on the object | |
def fido.bark; "I'm #{@name}" end | |
ap fido.bark | |
ap "-> fido.singleton_class.instance_methods(false) # There it is, in this objects singleton class" | |
ap fido.singleton_class.instance_methods(false) | |
binding.pry | |
# But there's another way, a little more common: Modules | |
# because modules are just bags of methods: | |
module Bark | |
def bark; "I'm #{@name}" end | |
end | |
lassie = Dog.new 'lassie' | |
lassie.extend Bark | |
ap "-> lassie.singleton_class.ancestors # And extend simply adds a new ancestor to the singleton_class" | |
ap lassie.singleton_class.ancestors | |
ap lassie.bark | |
binding.pry | |
# Ok, then what does include do? | |
module Wag | |
def wag; "wagging for #{@name}" end | |
end | |
# The same thing, but it is a method of Class, so this won't work, because dog is an Object | |
# lassie.send :include, Wag # gives NoMethodError | |
# But this will work, because Dog inherits from Class | |
Dog.send :include, Wag | |
ap "-> Dog.ancestors # There is our Wag" | |
ap Dog.ancestors | |
ap "-> fido.singleton_class.ancestors # And now our object has both Bark and Wag" | |
ap fido.singleton_class.ancestors | |
ap fido.wag | |
binding.pry | |
# So we've figured out instance methods, but then how do class methods work? | |
# Exact same way, because remember every class is an instance of Class, and | |
# instances have a singleton_class | |
ap "-> Dog.singleton_class # An instance of Class with Dog as it's argument" | |
ap Dog.singleton_class | |
binding.pry | |
# And as before there are lots of ways we can add methods to Dog's singleton_class, | |
# thereby creating class methods: | |
module Breeds | |
def breeds; [:collie, :poodle, @extra] end | |
end | |
def Dog.breeds; [:collie, :poodle, @extra] end | |
class Dog | |
@extra = :lab | |
# Remember, extend adds ancestors to the singleton_class, so this is equivalent to | |
# self.send :include, Breeds | |
extend Breeds | |
def self.breeds; [:collie, :poodle, @extra] end | |
class << self | |
def breeds; [:collie, :poodle, @extra] end | |
end | |
end | |
ap "-> Dog.singleton_class.instance_methods(false) # Also as before, the method lives in the singleton_class" | |
ap Dog.singleton_class.instance_methods(false) | |
ap "-> fido.singleton_class.ancestors # And Breeds was added to the lookup list" | |
ap Dog.singleton_class.ancestors | |
ap "-> Dog.instance_variable_get(:@name) # And since Dog is an instance of Class, and thus an object, it can have data and we can have class variables" | |
ap Dog.instance_variable_get(:@name) | |
ap Dog.breeds | |
binding.pry | |
# And hopefully this makes sense out of modules as well: | |
ap "-> Breed.class # They are instance of the Module class" | |
ap Breeds.class | |
ap "-> Breed.ancestors # And Module inherits from Class" | |
ap Breeds.ancestors | |
ap "-> Breed.singleton_class # As an instance of Module they have a singleton_class" | |
ap Breeds.singleton_class | |
module Breeds | |
# So you can instance variables to the singleton class | |
@shows = [1, 2] | |
# And you can extend the singleton class with a new ancestor, self, which is the same as `extend Breed` | |
extend self | |
def shows; @shows end | |
# Or you can define a method directly on the metaclass | |
def self.show_list; @shows.join(',') end | |
# Or you can create a module_function which makes it private and copies it to the singleton class | |
def reverse_list; @shows.reverse.join(',') end | |
module_function :reverse_list | |
end | |
ap Breeds.singleton_class.ancestors | |
ap Breeds.singleton_class.instance_methods(false) | |
binding.pry |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment