Metaprogramming in Ruby
-
Upload
nicolo-calcavecchia -
Category
Technology
-
view
615 -
download
1
Transcript of Metaprogramming in Ruby
Metaprogramming in Ruby
Nicola Calcavecchia - 24/04/2013calcavecchia@{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! 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
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
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! endend
obj = MyClass.newobj.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 # => ModuleModule.superclass # => Object
String.superclass # => ObjectObject.superclass # => BasicObjectBasicObject.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; endclass 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'! endend
class C! include Mend
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! # => MyClassend
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! endend
obj = MyClass.newobj.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.newobj.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 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
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
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 == selfend
str1.title?!# => falsestr2.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! endend
#str1title?
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_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
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
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
References
23