Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Messenger: The (Complete) Story of Method Lookup

Jay McGavren
November 16, 2015

Messenger: The (Complete) Story of Method Lookup

My talk on Ruby method lookup for RubyConf 2015.

Note that the original presentation relied heavily on animations, which are omitted on Speaker Deck. Be sure to look up the video on http://confreaks.tv/ once they post it!

Jay McGavren

November 16, 2015
Tweet

Other Decks in Technology

Transcript

  1. Singleton methods Methods that are defined on only a single

    object. If you've stubbed methods for a test, you've used singleton methods. class WebSpider def get(address, path) Net::HTTP.get_response(address, path).body end end spider = WebSpider.new puts spider.get("google.com", "/search") In your test: Slow, fragile method
  2. Methods that are defined on only a single object. If

    you've stubbed methods for a test, you've used singleton methods. class WebSpider def get(address, path) Net::HTTP.get_response(address, path).body end end spider = WebSpider.new def spider.get(address, path) "<html>...</html>" end puts spider.get("google.com", "/search") In your test: So define a faster singleton method instead! Singleton methods
  3. Singleton methods To create a singleton method, we need an

    object. Before we can create an object, we need a class. class MyClass end MyClass
  4. Singleton methods When we create an instance, behind the scenes,

    Ruby creates a "singleton class" that is specific to that object. class MyClass end ! instance = MyClass.new MyClass singleton Singleton class New instance
  5. class MyClass end ! instance = MyClass.new ! p instance.singleton_class

    #<Class:#<MyClass:0x007f84e1830ac8>> Access the singleton class of "instance" Singleton methods You can access any object's singleton class via the singleton_class method. MyClass singleton
  6. class MyClass end ! instance = MyClass.new ! p instance.singleton_class

    #<Class:#<MyClass:0x007f84e1830ac8>> Singleton methods Why does Ruby do this? Consistency. The same logic that lets you call methods on an object's class also lets you call methods on its singleton class. MyClass singleton
  7. Singleton methods When we define singleton methods on an object,

    they're added to its singleton class. The singleton class makes the methods available exclusively to that object. class MyClass end ! instance = MyClass.new ! def instance.my_method puts "Singleton method" end MyClass singleton Define a singleton method my_method Lives on singleton class
  8. Singleton methods When you call a method on an object,

    Ruby dispatches a message to that object, looking for that particular method. class MyClass end ! instance = MyClass.new ! def instance.my_method puts "Singleton method" end ! instance.my_method my_method Calling an instance method
  9. Singleton methods Ruby's first stop in its search for a

    method is always the object's singleton class. class MyClass end ! instance = MyClass.new ! def instance.my_method puts "Singleton method" end ! instance.my_method MyClass singleton my_method
  10. Singleton methods And as soon as Ruby finds a method

    with a matching name, it invokes it. class MyClass end ! instance = MyClass.new ! def instance.my_method puts "Singleton method" end ! instance.my_method MyClass singleton my_method Singleton method Output
  11. Instance methods Now, what would happen if we got rid

    of the singleton method... class MyClass end ! instance = MyClass.new ! def instance.my_method puts "Singleton method" end MyClass singleton my_method
  12. Instance methods ...and defined an instance method on the class,

    instead? class MyClass def my_method puts "Instance method" end end ! instance = MyClass.new ! MyClass singleton my_method Instance method
  13. Instance methods If we call my_method on the object, Ruby

    looks on the singleton class first, but there's no method there! Where can the method be found? MyClass singleton my_method class MyClass def my_method puts "Instance method" end end ! instance = MyClass.new class MyClass def my_method puts "Instance method" end end ! instance = MyClass.new ! instance.my_method
  14. class MyClass def my_method puts "Instance method" end end !

    instance = MyClass.new ancestors is your cheat sheet Each class maintains a pointer to the next class Ruby should look on for methods.
  15. class MyClass def my_method puts "Instance method" end end !

    instance = MyClass.new ! p instance.singleton_class.ancestors ancestors is your cheat sheet You can access this list of classes via the ancestors class method. [#<Class:#<MyClass:0x007fb9b0984ba0>>, MyClass, Object, Kernel, BasicObject] Call the ancestors method on the singleton class.
  16. ancestors is your cheat sheet You can access this list

    of classes via the ancestors class method. [#<Class:#<MyClass:0x007fb9b0984ba0>>, MyClass, Object, Kernel, BasicObject] Singleton class Class
  17. Instance methods When Ruby fails to find the method on

    the singleton class, it's directed to the next class on the chain: MyClass. MyClass singleton my_method class MyClass def my_method puts "Instance method" end end ! instance = MyClass.new ! instance.my_method [#<Class:#<MyClass:0x007fb9b0984ba0>>, MyClass, Object, Kernel, BasicObject] Look here next!
  18. Instance methods When Ruby fails to find the method on

    the singleton class, it's directed to the next class in the chain: MyClass. MyClass singleton my_method class MyClass def my_method puts "Instance method" end end ! instance = MyClass.new ! instance.my_method Instance method
  19. Inherited instance methods Suppose we move my_method again, to a

    superclass of MyClass. MyClass singleton my_method class MySuperclass def my_method puts "Instance method" end end ! class MyClass < MySuperclass end ! instance = MyClass.new ! MySuperclass Method defined in this class... ...which this class is a subclass of. Create an instance of the subclass.
  20. class MySuperclass def my_method puts "Instance method" end end !

    class MyClass < MySuperclass end ! instance = MyClass.new ! p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007faa719e51b0>>, MyClass, MySuperclass, Object, Kernel, BasicObject] Singleton class Instance's class Superclass If we call ancestors on the singleton class again, we can see all the places Ruby will look for my_method.
  21. Inherited instance methods MyClass singleton my_method MySuperclass [#<Class:#<MyClass:0x007faa719e51b0>>, MyClass, MySuperclass,

    Object, Kernel, BasicObject] Singleton class Instance's class Superclass Instance method
  22. Method overriding Now, what happens if a method appears in

    more than one place in the lookup chain? MyClass singleton my_method class MySuperclass def my_method puts "MySuperclass" end end ! class MyClass < MySuperclass def my_method puts "MyClass" end end ! instance = MyClass.new ! MySuperclass Method defined in this class... ...and its subclass my_method
  23. Method overriding If a subclass method has the same name

    as a superclass method, it "overrides" the superclass method. MyClass singleton my_method class MySuperclass def my_method puts "MySuperclass" end end ! class MyClass < MySuperclass def my_method puts "MyClass" end end ! instance = MyClass.new ! MySuperclass Overrides the method from the superclass my_method
  24. Method overriding Ruby simply invokes the first method with a

    name matching the one it's looking for, and stops. MyClass singleton my_method class MySuperclass def my_method puts "MySuperclass" end end ! class MyClass < MySuperclass def my_method puts "MyClass" end end ! instance = MyClass.new ! instance.my_method MySuperclass my_method MyClass Doesn't get invoked!
  25. "super" But what if you want to call the overridden

    method? MyClass singleton my_method class MySuperclass def my_method puts "MySuperclass" end end ! class MyClass < MySuperclass def my_method puts "MyClass" end end ! instance = MyClass.new ! instance.my_method MySuperclass my_method MyClass
  26. "super" Using the super keyword in the overriding method causes

    Ruby to resume its search, on the next class in the chain. MyClass singleton my_method class MySuperclass def my_method puts "MySuperclass" end end ! class MyClass < MySuperclass def my_method puts "MyClass" super end end ! instance = MyClass.new ! instance.my_method MySuperclass my_method MyClass Just use the "super" keyword! MySuperclass
  27. method_missing First, Ruby searches the entire ancestors chain looking for

    the method you called. class MyClass end ! instance = MyClass.new ! instance.nonexistent(1, 2) singleton MyClass Kernel MySuperclass Object BasicObject method_missing
  28. method_missing If the method's not found, another method is called,

    named method_missing, and the search starts over. class MyClass end ! instance = MyClass.new ! instance.nonexistent(1, 2) singleton MyClass Kernel MySuperclass Object BasicObject method_missing
  29. method_missing There's a default version of method_missing on BasicObject that

    raises an exception. (That's probably what you're used to seeing.) class MyClass end ! instance = MyClass.new ! instance.nonexistent(1, 2) undefined method `nonexistent' for #<MyClass: 0x007f975a135698> (NoMethodError) singleton MyClass Kernel MySuperclass Object BasicObject method_missing
  30. method_missing But you can also override method_missing in your own

    classes. method_missing class MySuperclass def method_missing(name, arg1, arg2) puts "method_missing" p name, arg1, arg2 end end ! class MyClass < MySuperclass end ! instance = MyClass.new singleton MyClass MySuperclass Object etc... Override method_missing to print the name of the called method, plus 2 arguments.
  31. method_missing First, Ruby will look for the method name that

    you call... method_missing class MySuperclass def method_missing(name, arg1, arg2) puts "method_missing" p name, arg1, arg2 end end ! class MyClass < MySuperclass end ! instance = MyClass.new ! instance.nonexistent(1, 2) singleton MyClass MySuperclass Object etc...
  32. method_missing If it doesn't find it, method_missing is called. method_missing

    class MySuperclass def method_missing(name, arg1, arg2) puts "method_missing" p name, arg1, arg2 end end ! class MyClass < MySuperclass end ! instance = MyClass.new ! instance.nonexistent(1, 2) singleton MyClass MySuperclass Object etc... method_missing :nonexistent 1 2
  33. You know class methods: methods you can call on the

    class itself, without needing to create an instance of it first. Class methods class MyClass ! def self.class_method puts "class_method" end ! end ! MyClass.class_method class_method def self.method_name instead of just def method_name
  34. There are alternate ways to define them, though... Class methods

    class MyClass ! def MyClass.class_method puts "class_method" end ! end ! MyClass.class_method class_method You can use the class constant instead of self...
  35. There are alternate ways to define them, though... Class methods

    class MyClass end ! def MyClass.class_method puts "class_method" end ! MyClass.class_method class_method Or you can define it outside of the class altogether...
  36. class_method Class methods class MyClass end ! def MyClass.class_method puts

    "class_method" end ! MyClass.class_method ! instance = Object.new ! def instance.my_method puts "singleton method" end ! instance.my_method Now, compare this class method declaration to a singleton method declaration.... Pretty similar, right? singleton method
  37. Class methods class MyClass end ! def MyClass.class_method puts "class_method"

    end ! MyClass.class_method class_method ! instance = Object.new ! def instance.my_method puts "singleton method" end ! instance.my_method In Ruby, a class method is just a singleton method on the class! singleton method
  38. Class methods class MyClass end ! And since the class

    is an object, it has a singleton class of its own. Class singleton
  39. Class methods class MyClass end ! def MyClass.class_method puts "class_method"

    end And that singleton class lets us define singleton methods on it, of course. Class singleton class_method
  40. Class methods class MyClass end ! def MyClass.class_method puts "class_method"

    end ! MyClass.class_method class_method When you call a class method, Ruby finds it on the singleton class, and invokes it. Class singleton class_method
  41. Class methods class MyClass end ! p MyClass.class Class You

    can confirm for yourself that MyClass is an instance of Class... The class of MyClass
  42. Class methods class MyClass end ! p MyClass.class ! p

    MyClass.singleton_class Class ...And that MyClass has a singleton class. The class of MyClass The singleton class of MyClass #<Class:MyClass>
  43. Class methods class MyClass ! def self.class_method puts "class_method" end

    ! end ! MyClass.class_method Within the body of a class, Ruby sets self to point to that class. Equivalent to def MyClass.class_method A singleton method on the class!
  44. What about methods defined at the top level? Methods defined

    in main def my_method puts "my_method" end ! my_method
  45. Within a Ruby source file, you can just declare methods

    and call them, without surrounding them in a class or module. Methods defined in main def my_method puts "my_method" end ! my_method Not within any class my_method
  46. Top-level methods just get defined as private methods on the

    Object class! Methods defined in main def my_method puts "my_method" end my_method Object
  47. Since all other classes inherit from Object... Methods defined in

    main def my_method puts "my_method" end ! class MyClass end MyClass my_method Object
  48. ...you can call methods defined at the top level from

    any instance method, of any class. Methods defined in main def my_method puts "my_method" end ! class MyClass def call_my_method my_method end end Can be called from anywhere! MyClass my_method Object Not within any class
  49. ...you can call methods defined at the top level from

    any instance method, of any class. Methods defined in main def my_method puts "my_method" end ! class MyClass def call_my_method my_method end end ! instance = MyClass.new instance.call_my_method my_method Can be called from anywhere! MyClass singleton my_method Object Not within any class
  50. But then how can you call the method at the

    top level? Methods defined in main def my_method puts "my_method" end ! my_method my_method This is a private method, and we're not within a class!
  51. Ruby just sets self to an instance of the Object

    class at the top level! Methods defined in main def my_method puts "my_method" end ! puts self puts self.class main Object At the top level, self is set to an instance of Object!
  52. Methods defined in main def my_method puts "my_method" end !

    my_method my_method self is the implicit receiver of this method call, and it's an instance of Object! Ruby just sets self to an instance of the Object class at the top level!
  53. Mixins You can define methods on modules... my_method module MyModule

    def my_method puts "MyModule" end end MyModule
  54. Mixins You can also use a module kind of like

    a superclass, by "mixing" the module into a class. MyClass module MyModule def my_method puts "MyModule" end end ! class MyClass include MyModule end my_method MyModule Mix in MyModule
  55. Mixins Doing so adds the module to the ancestors list

    of the class. MyClass module MyModule def my_method puts "MyModule" end end ! class MyClass include MyModule end ! p MyClass.ancestors my_method MyModule [MyClass, MyModule, Object, Kernel, BasicObject] Class Module
  56. Mixins In fact, internally, Ruby treats the module as if

    it was a class! MyClass module MyModule def my_method puts "MyModule" end end ! class MyClass include MyModule end ! p MyClass.ancestors my_method MyModule [MyClass, MyModule, Object, Kernel, BasicObject] Class Module
  57. Mixins That means that method lookup works the same with

    a mixin as with a superclass. MyClass module MyModule def my_method puts "MyModule" end end ! class MyClass include MyModule end ! instance = MyClass.new ! instance.my_method my_method MyModule singleton Create an instance Call the method MyModule
  58. Overriding Mixins Since mixins use the same lookup mechanism as

    classes, all the same rules apply. Like overriding... MyClass module MyModule def my_method puts "MyModule" end end ! class MyClass include MyModule def my_method puts "MyClass" end end ! instance = MyClass.new ! instance.my_method my_method MyModule singleton Override the mixin method MyClass my_method Doesn't get invoked!
  59. Overriding Mixins Since mixins use the same lookup mechanism as

    classes, all the same rules apply. Like overriding... and super. MyClass module MyModule def my_method puts "MyModule" end end ! class MyClass include MyModule def my_method puts "MyClass" super end end ! instance = MyClass.new ! instance.my_method my_method MyModule singleton Call the overridden module method MyClass my_method MyModule
  60. When include won't work You can override a method from

    a module with a method from a class. But not vice-versa. Not if you use include. MyClass module MyModule def my_method puts "Good thing I'm here!" end end ! class MyClass include MyModule def my_method fail "Not implemented" end end ! instance = MyClass.new ! instance.my_method my_method MyModule singleton ...But include adds module after class in lookup chain in `my_method': Not implemented (RuntimeError) my_method Method from class overrides method from module! We want this to override method from class...
  61. When include won't work The ancestors method shows us that

    the module appears after the class in the lookup chain. module MyModule def my_method puts "Good thing I'm here!" end end ! class MyClass include MyModule def my_method fail "Not implemented" end end ! p MyClass.ancestors [MyClass, MyModule, Object, Kernel, BasicObject] Class Module Call "ancestors"
  62. Prepending modules If you use the prepend method to mix

    in the module instead, it will be added to the lookup chain before the class. module MyModule def my_method puts "Good thing I'm here!" end end ! class MyClass prepend MyModule def my_method fail "Not implemented" end end ! p MyClass.ancestors [MyModule, MyClass, Object, Kernel, BasicObject] Module Class Call "ancestors" prepend adds module before class in lookup chain
  63. Prepending modules prepend allows the module method to override the

    class method. MyClass module MyModule def my_method puts "Good thing I'm here!" end end ! class MyClass prepend MyModule def my_method fail "Not implemented" end end ! instance = MyClass.new ! instance.my_method singleton prepend adds module before class in lookup chain Good thing I'm here! my_method Method from module overrides method from class! my_method MyModule
  64. Suppose you want to change the way String#capitalize works. Refinements

    class MovieTitle def title puts "the matrix".capitalize end end ! MovieTitle.new.title The matrix But capitalize capitalizes only the first word You have a bunch of strings you want in Title Case
  65. So, you re-open the String class, and redefine the capitalize

    method. (You "monkey-patch" it.) Refinements class String def capitalize words = split(" ") words.map! do |w| w[0].upcase + w[1..-1] end words.join(" ") end end Change String#capitalize to convert strings to Title Case
  66. Refinements class String def capitalize words = split(" ") words.map!

    do |w| w[0].upcase + w[1..-1] end words.join(" ") end end class MovieTitle def title puts "the matrix".capitalize end end ! MovieTitle.new.title The Matrix This works great for your MovieTitle class! class Sentence def sentence puts "That's wrong.".capitalize end end ! Sentence.new.sentence That's Wrong. ...But not so great for your Sentence class.
  67. class String def capitalize words = split(" ") words.map! do

    |w| w[0].upcase + w[1..-1] end words.join(" ") end end Just take your monkey patch from the String class... Situations like this are why refinements were created.
  68. module MyCapitalize refine String do def capitalize words = split("

    ") words.map! do |w| w[0].upcase + w[1..-1] end words.join(" ") end end end ...And convert it to a refinement! (Sorry, no time to cover refinements syntax here. Just Google "ruby refinements".)
  69. module MyCapitalize refine String do def capitalize words = split("

    ") words.map! do |w| w[0].upcase + w[1..-1] end words.join(" ") end end end A refinement is a module that prepends an existing class, but only within lexical scopes (files, classes or modules) that explicitly activate it.
  70. module MyCapitalize refine String do def capitalize words = split("

    ") words.map! do |w| w[0].upcase + w[1..-1] end words.join(" ") end end end class MovieTitle using MyCapitalize def title puts "the matrix".capitalize end end ! MovieTitle.new.title The Matrix String#capitalize is overridden, but only within MovieTitle. class Sentence def sentence puts "That's better.".capitalize end end ! Sentence.new.sentence That's better. Now that your refinement is defined... You can activate it within MovieTitle. Outside of MovieTitle, we still get the original capitalize!
  71. String capitalize #<refinement: String@MyCapitalize> The Matrix module MyCapitalize refine String

    do def capitalize words = split(" ") words.map! do |w| w[0].upcase + w[1..-1] end words.join(" ") end end end ! class MovieTitle using MyCapitalize end ! class Sentence end capitalize The matrix String capitalize MovieTitle Sentence Now, the version of capitalize you get depends on your starting point! A scope without refinements ...A scope where the refinement has been activated
  72. MyClass class MyClass ! end ! ! ! ! !

    ! ! p MyClass.ancestors [MyClass, Object, Kernel, BasicObject]
  73. singleton MyClass class MyClass ! end ! ! ! !

    ! ! ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, MyClass, ...]
  74. singleton MyClass MySuperclass class MyClass < MySuperclass ! end !

    ! ! ! ! ! ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, MyClass, MySuperclass, ...]
  75. singleton MyClass MyModule MySuperclass class MyClass < MySuperclass include MyModule

    ! end ! ! ! ! ! ! ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, MyClass, MyModule, MySuperclass, ...]
  76. singleton MyClass Prepended Module MyModule MySuperclass class MyClass < MySuperclass

    include MyModule prepend PrependedModule end ! ! ! ! ! ! ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, PrependedModule, MyClass, MyModule, MySuperclass, ...]
  77. singleton MyClass Prepended Module MyModule MySuperclass class MyClass < MySuperclass

    include MyModule prepend PrependedModule end ! module MyRefinement refine MyClass do end end ! ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, PrependedModule, MyClass, MyModule, MySuperclass, ...]
  78. #<refinement: MyClass@MyRefinement> singleton MyClass Prepended Module MyModule MySuperclass class MyClass

    < MySuperclass include MyModule prepend PrependedModule end ! module MyRefinement refine MyClass do end end using MyRefinement ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, PrependedModule, MyClass, MyModule, MySuperclass, ...]
  79. #<refinement: MyClass@MyRefinement> singleton MyClass Prepended Module MyModule MySuperclass class MyClass

    < MySuperclass include MyModule prepend PrependedModule end ! module MyRefinement refine MyClass do end end using MyRefinement ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, PrependedModule, MyClass, MyModule, MySuperclass, ...] my_method my_method my_method my_method my_method my_method
  80. #<refinement: MyClass@MyRefinement> singleton MyClass Prepended Module MyModule MySuperclass class MyClass

    < MySuperclass include MyModule prepend PrependedModule end ! module MyRefinement refine MyClass do end end using MyRefinement ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, PrependedModule, MyClass, MyModule, MySuperclass, ...] my_method instance.my_method
  81. #<refinement: MyClass@MyRefinement> singleton MyClass Prepended Module MyModule MySuperclass class MyClass

    < MySuperclass include MyModule prepend PrependedModule end ! module MyRefinement refine MyClass do end end using MyRefinement ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, PrependedModule, MyClass, MyModule, MySuperclass, ...] my_method my_method instance.my_method
  82. #<refinement: MyClass@MyRefinement> singleton MyClass Prepended Module MyModule MySuperclass class MyClass

    < MySuperclass include MyModule prepend PrependedModule end ! module MyRefinement refine MyClass do end end using MyRefinement ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, PrependedModule, MyClass, MyModule, MySuperclass, ...] my_method my_method instance.my_method What questions do you have?
  83. Resources Singleton classes, modules, etc. Start here! The Well Grounded

    Rubyist (2nd ed.) David A. Black Manning, 2014 Ruby Under a Microscope Pat Shaughnessy No Starch Press, 2014 RubyTapas - Refinements http://rubytapas.com/episodes/250-Refinements Ruby internals. Hardcore. This episode is free!
  84. Find me during RubyConf for discount codes on the book!

    Want to get your friends hooked on Ruby?