Metaprogramming in Ruby Nicola Calcavecchia - 24/04/2013 calcavecchia@{elet.polimi.it|gmail.com} Principles of Programming Languages 1
Metaprogramming • Writing code that manipulates language constructs at runtime • In Ruby no distinction: • Same as “regular” programming • Enables: • Code that writes code • DSLs • Introspection (e.g., reflection) 2
Introspection • Allows to get information about objects at runtime • Methods • Instance variables • etc. class MyClass ! def initialize() ! ! @var1, @var2 = 0, 2 ! end ! def my_method ! end end m = MyClass.new m.class => MyClass m.methods => # lot of methods ... m.instance_variables => [:@var1, :@var2] m.public_methods => # ... m.private_methods => # ... m.instance_of? MyClass => true m.instance_of? Object => false m.is_a? MyClass => true m.is_a? Object => true 3
Open Classes • Classes can be “opened” for change • What about the open/closed principle? class MyClass ! def a; "method a"; end end m = MyClass.new m.methods - Object.new.methods => [:a] class MyClass ! def b; "method b"; end end m.methods - Object.new.methods => [:a, :b] class Numeric ! KILOBYTE = 1024 ! def kilobytes ! ! self * KILOBYTE ! end end puts 2.kilobytes => 2048 4
Monkeypatching • Refers to the general idea of modifying the runtime code without modifying the original source code • Problems: • Redefining existing methods • Change methods used in other pieces of the code • Ruby 2.0 introduced “scoped” monkeypatching 5
Objects and classes • Objects contains instance variables • Remember: instance variables exists only when assigned • Objects contains: • Instance variables • Reference to its class • object_id class MyClass ! def my_method ! ! @v = 1 ! end end obj = MyClass.new obj.my_method obj1 @v = 1 MyClass my_method() object instance variables class methods class 6
Classes are objects too • class • superclass • All objects inherit from BasicObject Class.superclass # => Module Module.superclass # => Object String.superclass # => Object Object.superclass # => BasicObject BasicObject.superclass # => nil 7
Classes and superclasses obj1 obj2 MyClass Object Class Module class class class superclass superclass •What’s the class of Object ? •What’s the superclass of Module ? •What’s the class of Class ? class MyClass; end obj1 = MyClass.new obj2 = MyClass.new BasicObject superclass nil superclass 8
Invoking methods • Calling a method involves two steps: 1. Method lookup • Identify receiver class • Escalate ancestor chain until the method is found 2. Method execution • The actual code is executed class A; end class B < A; end B.ancestors => [B, A, Object, Kernel, BasicObject] The ancestor chain includes also modules 9
Ancestor chains module M ! def my_method ! ! 'M#my_method' ! end end class C ! include M end class D < C; end D C M Object Kernel BasicObject 10
self • Every line of Ruby is executed within an object • Called current object: self • Only one object holds the self at any given time • Instance variables and methods (without explicit receiver) are called on self • In class or module definition the role of self is taken by the class or module class MyClass ! self! # => MyClass end 11
Calling methods dynamically • Remember “sending messages to objects” ? • Method send sends messages to objects • Can be used to dynamically call methods class MyClass ! def my_method(my_arg) ! ! my_arg * 2 ! end end obj = MyClass.new obj.send(:my_method, 3)! # => 6 12
Defining methods dynamically • Use the Module#define_method method • Provide a block for the method body class MyClass ! ["steve", "jeff", "larry"].each{|d| ! ! ! define_method d.to_sym do ! ! ! ! puts d ! ! ! end ! ! } end obj = MyClass.new obj.steve! # => "steve" obj.jeff! # => "jeff" obj.larry! # => "larry" 13
method_missing • What happens if no method is found in the ancestor hierarchy? • A method called method_missing is called • A common idiom is to override this method in order to intercept unknown messages • Define ghost methods • Methods that do not actually exists! 14
An example class Mapper ! def initialize() ! ! @map = {} ! end ! def add(key, value) ! ! @map[key.downcase] = value ! end ! def method_missing(method_name, *args) ! ! key = method_name.to_s.downcase ! ! return @map[key] if @map.key? key ! end end m = Mapper.new m.add("Rome","IT") m.add("London","UK") puts m.rome puts m.london 15
instance_eval • Allows to evaluate a piece of code within the scope of an object • That is: changes the self for a piece of code class MyClass ! def initialize ! ! @v = 1 ! end end obj = MyClass.new obj.instance_eval do ! self!# => #<MyClass:0x83fd33 @v=1> ! @v! ! # => 1 end v = 2 obj.instance_eval { @v = v} obj.instance_eval { @v }! # => 2 BREAKS ENCAPSULATION! Read/write private data With great power comes great responsibility! 16
class_eval • Evaluates a block in the context of an existing class • Changes the self (i.e., it reopens the class) def add_method_to_(a_class) ! a_class.class_eval do ! ! def m ! ! ! "Hello!" ! ! end ! end!! end add_method_to String "abc".m ! # => "Hello!" More flexible than reopening it with the class keyword (i.e., parametric) 17
Singleton methods • In Ruby it is possible to add a method to a single instance of an object str1 = "This is a string!" str2 = "Another str" def str1.title? ! self.upcase == self end str1.title?!# => false str2.title? => NoMethodError: undefined method `title?' for "Another str":String 18
Singleton methods - 2 • Are stored in special classes called eigenclasses • Invoking Object#class does not show eigenclasses • Special syntax to enter in their scope class << str1 ! # Eigenclass scope ! def title? ! ! upcase == self ! end end #str1 title? String Object str1 superclass superclass Eigenclass Method lookup revisited 19
Method aliases • Introduce new names for methods class MyClass ! def my_method; 'my_method()'; end ! alias :m :my_method end obj = MyClass.new obj.my_method! # => "my_method()" obj.m ! ! ! # => "my_method()" class String ! alias :real_length :length ! def length ! ! real_length > 5 ? 'long' : 'short' ! end end We can invoke the method with two names Redefine but still use the old one (this is also called around alias) 20
Kernel#eval • Instead of taking a block, it takes a string containing the code • Code is executed and the result of expression is returned a = 1 b = 2 c = eval("a + b") puts c => 3 Problems • Code injection • Readability • etc. 21
Hook methods • Various aspects of classes and method definition can be caught at runtime • Similar to events • For example method_missing class String ! def self.inherited(subclass) ! ! puts "#{self} was inherited by #{subclass}" ! end end class MyString < String; end => "String was inherited by MyString" module M ! def self.included(other_mod) ! ! "M was mixed into #{other_mod}" ! end end class C ! include M end => "M was mixed into C" 22
References 23

Metaprogramming in Ruby

  • 1.
    Metaprogramming in Ruby Nicola Calcavecchia- 24/04/2013 calcavecchia@{elet.polimi.it|gmail.com} Principles of Programming Languages 1
  • 2.
    Metaprogramming • Writing codethat manipulates language constructs at runtime • In Ruby no distinction: • Same as “regular” programming • Enables: • Code that writes code • DSLs • Introspection (e.g., reflection) 2
  • 3.
    Introspection • Allows toget information about objects at runtime • Methods • Instance variables • etc. class MyClass ! def initialize() ! ! @var1, @var2 = 0, 2 ! end ! def my_method ! end end m = MyClass.new m.class => MyClass m.methods => # lot of methods ... m.instance_variables => [:@var1, :@var2] m.public_methods => # ... m.private_methods => # ... m.instance_of? MyClass => true m.instance_of? Object => false m.is_a? MyClass => true m.is_a? Object => true 3
  • 4.
    Open Classes • Classescan be “opened” for change • What about the open/closed principle? class MyClass ! def a; "method a"; end end m = MyClass.new m.methods - Object.new.methods => [:a] class MyClass ! def b; "method b"; end end m.methods - Object.new.methods => [:a, :b] class Numeric ! KILOBYTE = 1024 ! def kilobytes ! ! self * KILOBYTE ! end end puts 2.kilobytes => 2048 4
  • 5.
    Monkeypatching • Refers tothe general idea of modifying the runtime code without modifying the original source code • Problems: • Redefining existing methods • Change methods used in other pieces of the code • Ruby 2.0 introduced “scoped” monkeypatching 5
  • 6.
    Objects and classes •Objects contains instance variables • Remember: instance variables exists only when assigned • Objects contains: • Instance variables • Reference to its class • object_id class MyClass ! def my_method ! ! @v = 1 ! end end obj = MyClass.new obj.my_method obj1 @v = 1 MyClass my_method() object instance variables class methods class 6
  • 7.
    Classes are objectstoo • class • superclass • All objects inherit from BasicObject Class.superclass # => Module Module.superclass # => Object String.superclass # => Object Object.superclass # => BasicObject BasicObject.superclass # => nil 7
  • 8.
    Classes and superclasses obj1 obj2 MyClass Object Class Module class class class superclasssuperclass •What’s the class of Object ? •What’s the superclass of Module ? •What’s the class of Class ? class MyClass; end obj1 = MyClass.new obj2 = MyClass.new BasicObject superclass nil superclass 8
  • 9.
    Invoking methods • Callinga method involves two steps: 1. Method lookup • Identify receiver class • Escalate ancestor chain until the method is found 2. Method execution • The actual code is executed class A; end class B < A; end B.ancestors => [B, A, Object, Kernel, BasicObject] The ancestor chain includes also modules 9
  • 10.
    Ancestor chains module M !def my_method ! ! 'M#my_method' ! end end class C ! include M end class D < C; end D C M Object Kernel BasicObject 10
  • 11.
    self • Every lineof Ruby is executed within an object • Called current object: self • Only one object holds the self at any given time • Instance variables and methods (without explicit receiver) are called on self • In class or module definition the role of self is taken by the class or module class MyClass ! self! # => MyClass end 11
  • 12.
    Calling methods dynamically • Remember“sending messages to objects” ? • Method send sends messages to objects • Can be used to dynamically call methods class MyClass ! def my_method(my_arg) ! ! my_arg * 2 ! end end obj = MyClass.new obj.send(:my_method, 3)! # => 6 12
  • 13.
    Defining methods dynamically • Usethe Module#define_method method • Provide a block for the method body class MyClass ! ["steve", "jeff", "larry"].each{|d| ! ! ! define_method d.to_sym do ! ! ! ! puts d ! ! ! end ! ! } end obj = MyClass.new obj.steve! # => "steve" obj.jeff! # => "jeff" obj.larry! # => "larry" 13
  • 14.
    method_missing • What happensif no method is found in the ancestor hierarchy? • A method called method_missing is called • A common idiom is to override this method in order to intercept unknown messages • Define ghost methods • Methods that do not actually exists! 14
  • 15.
    An example class Mapper !def initialize() ! ! @map = {} ! end ! def add(key, value) ! ! @map[key.downcase] = value ! end ! def method_missing(method_name, *args) ! ! key = method_name.to_s.downcase ! ! return @map[key] if @map.key? key ! end end m = Mapper.new m.add("Rome","IT") m.add("London","UK") puts m.rome puts m.london 15
  • 16.
    instance_eval • Allows toevaluate a piece of code within the scope of an object • That is: changes the self for a piece of code class MyClass ! def initialize ! ! @v = 1 ! end end obj = MyClass.new obj.instance_eval do ! self!# => #<MyClass:0x83fd33 @v=1> ! @v! ! # => 1 end v = 2 obj.instance_eval { @v = v} obj.instance_eval { @v }! # => 2 BREAKS ENCAPSULATION! Read/write private data With great power comes great responsibility! 16
  • 17.
    class_eval • Evaluates ablock in the context of an existing class • Changes the self (i.e., it reopens the class) def add_method_to_(a_class) ! a_class.class_eval do ! ! def m ! ! ! "Hello!" ! ! end ! end!! end add_method_to String "abc".m ! # => "Hello!" More flexible than reopening it with the class keyword (i.e., parametric) 17
  • 18.
    Singleton methods • InRuby it is possible to add a method to a single instance of an object str1 = "This is a string!" str2 = "Another str" def str1.title? ! self.upcase == self end str1.title?!# => false str2.title? => NoMethodError: undefined method `title?' for "Another str":String 18
  • 19.
    Singleton methods -2 • Are stored in special classes called eigenclasses • Invoking Object#class does not show eigenclasses • Special syntax to enter in their scope class << str1 ! # Eigenclass scope ! def title? ! ! upcase == self ! end end #str1 title? String Object str1 superclass superclass Eigenclass Method lookup revisited 19
  • 20.
    Method aliases • Introducenew names for methods class MyClass ! def my_method; 'my_method()'; end ! alias :m :my_method end obj = MyClass.new obj.my_method! # => "my_method()" obj.m ! ! ! # => "my_method()" class String ! alias :real_length :length ! def length ! ! real_length > 5 ? 'long' : 'short' ! end end We can invoke the method with two names Redefine but still use the old one (this is also called around alias) 20
  • 21.
    Kernel#eval • Instead oftaking a block, it takes a string containing the code • Code is executed and the result of expression is returned a = 1 b = 2 c = eval("a + b") puts c => 3 Problems • Code injection • Readability • etc. 21
  • 22.
    Hook methods • Variousaspects of classes and method definition can be caught at runtime • Similar to events • For example method_missing class String ! def self.inherited(subclass) ! ! puts "#{self} was inherited by #{subclass}" ! end end class MyString < String; end => "String was inherited by MyString" module M ! def self.included(other_mod) ! ! "M was mixed into #{other_mod}" ! end end class C ! include M end => "M was mixed into C" 22
  • 23.