Metaprogramming in Ruby

23
Metaprogramming in Ruby Nicola Calcavecchia - 24/04/2013 calcavecchia@{elet.polimi.it|gmail.com} Principles of Programming Languages 1

Transcript of Metaprogramming in Ruby

Page 1: Metaprogramming in Ruby

Metaprogramming in Ruby

Nicola Calcavecchia - 24/04/2013calcavecchia@{elet.polimi.it|gmail.com}

Principles of Programming Languages

1

Page 2: Metaprogramming in Ruby

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

References

23