metaprogramming in ruby
TRANSCRIPT
![Page 1: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/1.jpg)
Metaprogramming in Ruby
Nicola Calcavecchia - 24/04/2013calcavecchia@{elet.polimi.it|gmail.com}
Principles of Programming Languages
1
![Page 2: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/2.jpg)
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
![Page 3: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/3.jpg)
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! endend
m = MyClass.newm.class=> MyClassm.methods=> # lot of methods ...m.instance_variables=> [:@var1, :@var2]m.public_methods=> # ...m.private_methods=> # ...m.instance_of? MyClass=> truem.instance_of? Object=> falsem.is_a? MyClass=> truem.is_a? Object=> true
3
![Page 4: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/4.jpg)
Open Classes
• Classes can be “opened” for change
• What about the open/closed principle?
class MyClass! def a; "method a"; endendm = MyClass.newm.methods - Object.new.methods=> [:a]
class MyClass! def b; "method b"; endendm.methods - Object.new.methods=> [:a, :b]
class Numeric! KILOBYTE = 1024! def kilobytes! ! self * KILOBYTE! endend
puts 2.kilobytes=> 2048
4
![Page 5: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/5.jpg)
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
![Page 6: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/6.jpg)
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! endend
obj = MyClass.newobj.my_method
obj1
@v = 1
MyClass
my_method()
object
instance variables
class
methods
class
6
![Page 7: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/7.jpg)
Classes are objects too
• class
• superclass
• All objects inherit from BasicObject
Class.superclass # => ModuleModule.superclass # => Object
String.superclass # => ObjectObject.superclass # => BasicObjectBasicObject.superclass # => nil
7
![Page 8: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/8.jpg)
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
![Page 9: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/9.jpg)
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; endclass B < A; end
B.ancestors => [B, A, Object, Kernel, BasicObject]
The ancestor chain includes also modules
9
![Page 10: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/10.jpg)
Ancestor chains
module M! def my_method! ! 'M#my_method'! endend
class C! include Mend
class D < C; end
D
C
M
Object
Kernel
BasicObject
10
![Page 11: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/11.jpg)
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! # => MyClassend
11
![Page 12: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/12.jpg)
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! endend
obj = MyClass.newobj.send(:my_method, 3)! # => 6
12
![Page 13: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/13.jpg)
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.newobj.steve! # => "steve"obj.jeff! # => "jeff"obj.larry! # => "larry"
13
![Page 14: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/14.jpg)
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
![Page 15: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/15.jpg)
An exampleclass 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! endend
m = Mapper.newm.add("Rome","IT")m.add("London","UK")puts m.romeputs m.london
15
![Page 16: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/16.jpg)
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! endendobj = MyClass.newobj.instance_eval do! self!# => #<MyClass:0x83fd33 @v=1>! @v! ! # => 1end
v = 2obj.instance_eval { @v = v}obj.instance_eval { @v }! # => 2
BREAKS ENCAPSULATION!Read/write private data
With great power comes great responsibility!
16
![Page 17: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/17.jpg)
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
![Page 18: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/18.jpg)
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 == selfend
str1.title?!# => falsestr2.title?=> NoMethodError: undefined method `title?' for "Another str":String
18
![Page 19: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/19.jpg)
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! endend
#str1title?
String
Object
str1
superclass
superclass
Eigenclass
Method lookup revisited
19
![Page 20: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/20.jpg)
Method aliases
• Introduce new names for methods
class MyClass! def my_method; 'my_method()'; end! alias :m :my_methodendobj = MyClass.newobj.my_method! # => "my_method()"obj.m ! ! ! # => "my_method()"
class String! alias :real_length :length! def length! ! real_length > 5 ? 'long' : 'short'! endend
We can invoke the method with two names
Redefine but still use the old one (this is also called around alias)
20
![Page 21: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/21.jpg)
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 = 1b = 2c = eval("a + b")puts c=> 3
Problems
• Code injection
• Readability
• etc.
21
![Page 22: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/22.jpg)
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}"! endend
class MyString < String; end=> "String was inherited by MyString"
module M! def self.included(other_mod)! ! "M was mixed into #{other_mod}"! endendclass C! include Mend=> "M was mixed into C"
22
![Page 23: Metaprogramming in Ruby](https://reader036.vdocument.in/reader036/viewer/2022081400/55557712b4c9055f5f8b4c66/html5/thumbnails/23.jpg)
References
23