Metaprogramming: A Primerfiles.meetup.com/1138715/metaprogramming_part_1.pdf · Metaprogramming?...
Transcript of Metaprogramming: A Primerfiles.meetup.com/1138715/metaprogramming_part_1.pdf · Metaprogramming?...
Metaprogramming?
Ruby is a dynamic language. Interpreted at runtime, with late bindings, you can add/remove/change functions and variables of all kinds on the fly. You have the power to enhance the language and your program as desired. You also have the power to confuse and break things easily.
"Code that writes code"
"Writing code that manipulates language constructs at runtime"
Pre-reqs and Caveats
• MRI 1.9.2
• Things were (are) a little different in 1.8.x
• Non-MRI versions? Trust but verify
Object Model
class TestClass def one @first = 1 end endo = TestClass.newo.classo.instance_variableso.oneo.instance_variableso.methodso.methods.grep /^one/
load '02_inherit.rb't = SubTestClass.newt.one
load '03_reopen.rb'r = Reopen.newr.oner.two
ruby 'string_test_before.rb'ruby 'string_test_after.rb'
ruby 'monkey_test_before.rb'ruby 'monkey_test_after.rb'
Object
• Container for instance variables and methods
• [actually, not even methods, but a reference to its class]
• [[actually, actually, some other metadata too]]
Open Classes
• Use for
• simple changes to existing classes
• organizing code
• When you won't cause confusion (accidental monkeypatching)
• Consider alternatives: subclass, new class, singleton methods to specific instances
Objects have Class
• Instance variables live in object, methods live in class
TestClass
one()@first = 1
o
classobject
class
Objects' methods == Class' instance methodsClass' methods != Object's methodsSo objects share methods from their class, but have unique sets of instance variables
Classes are Objects
• ...so they follow the rules, such as having a class...
• "brains".class # => String
• String.class # => Class
• Class.instance_methods(inherited=false) # => [:allocate, :new, :superclass]
The methods of an object are the instance methods of its class. The methods of a class are the instance methods of Class.
Class.instance_methods(false)
Inheritance Chain
• String.superclass # => Object
• Object.superclass # => BasicObject
• BasicObject.superclass # => nil
And for Class
• Class.superclass # => Module
• Module.superclass # => Object
A class is a module with some extra methods to allow for creation of objects and inheritance
TestClass Class
new()...
ModuleObject
class()...
obj1
BasicObject
obj2
class
classclass
superclass
superclass
superclass
superclass
Everything is "held on to" via references in Ruby. obj1 and 2 are in variables of those names, but classes are stored in Constants (class names are simply Constants). We'll talk about constants another time, but you can think of them as a special kind of variable (with their own scoping rules) whose names start with an uppercase letter
Key Ideas
• An object is a collection of instance variables and link to a class. Its methods live in the class (the class's instance methods)
• A class is an object that is an instance of Class, plus a list of instance methods and a link to a superclass. Class is a subclass of Module, so classes are modules too.
A class has its own methods, such as new() provided by the Class class. References to classes are available via a constant defined as the class's name.
Pop Quiz
• What is the class of Object?
• What is the class of Class?
• What is the superclass of Module?
• What is the result of this code?
• obj3 = TestClass.new
• obj3.instance_variable_set("@sec",2)
TestClass Class
new()...
ModuleObject
class()...
obj1
BasicObject
obj2
class
classclass
superclass
superclass
superclass
superclass
superclass
class
class
obj3
@sec=2class
obj3 gets its own unique copy of the instance variable (recall that objects hold on to their own instance variable lists)
Methods
Two key ideas in calling methods, method lookup and execution (with current context of self)
Terminology
• receiver: the target object that you are calling the method on
• ancestors (chain): the series of superclasses of the current object, all the way to the root BasicObject
Object in 1.8
Method Lookup
• examine receiver's class for desired method, and if not found walk the ancestor chain
Demo
load '02_inherit.rb't = SubTestClass.newt.oneSubTestClass.ancestors
class TestClass def one 'TestClass#one' endend
class SubTestClass < TestClassend
o = SubTestClass.newo.one # => "TestClass#one"
SubTestClass.ancestors# => [SubTestClass, TestClass, Object, Kernel, BasicObject]
A-side of Modules
• Including a module alters lookup behavior
• Anonymous class is created ("include class")
• Include class is inserted into ancestor chain just above the including class
Proxy aka "Include" class
module M def one 'M#one' endend
class C include Mend
class S < C; end
S.new.one() # => "M#one"
S.ancestors # => [S, C, M, Object, Kernel, BasicObject]
load '08_modules.rb'
S.ancestors
That Sneaky Kernel
• Included by Object
• Contains many useful methods
• Since you are always inside some object, these methods seem omnipresent
• You can hang useful methods off it yourself, just don't hang your self
Multiple Modules?
• They keep getting inserted right before the class, so watch your ordering!
• class TestClass; include F; include L; end
• => [TestClass, L, F, Object, Kernel, BasicObject]
load '09_modules_many.rb'
Calling Methods
• What object should you call the method on?
• What object owns the instance variables used by the method?
Once method is found, the interpreter needs to answer two questions: what object does associated instance variables belong to and what object should you call the method on?
self• A reference to the receiver of the method
call: "The Current Object"
• Changes frequently to most recent receiver
• All instance vars are those of self
• All method calls without an explicit receiver are sent to self
• Using self outside of a method in class or module, self refers to class or module*
Top level is "main" (see in IRB)
The role of self in Classes will be handled later
Dynamic Method Calls
• Standard dot notation
• receiver.method(args)
• Object#send
• receiver.send(:method, args)
With send, you can use the dynamic dispatch technique to decide what to call at the last minute
load '10_sendexample.rb'
send can access private methodspublic_send (in 1.9) can not__send__ is available too
Defining Methods• Methods can be defined dynamically with
Module#define_method()
class TestClass define_method :times_ten do |my_arg| my_arg * 10 endend
o = TestClass.newo.times_ten(3) # => 30
Since define_method is executed in the scope of the class, the method becomes and instance method of that class
load '11_dynamicmethods.rb'
method_missing
• After walking the ancestor chain and not finding your method, Kernel#method_missing is called
• You can override and do something, creating a ghost method
load '12_dynamicmethods2.rb'
class MyOpenStruct def initialize @attributes = {} end def method_missing(name, *args) attribute = name.to_s if attribute =~ /=$/ @attributes[attribute.chop] = args[0] else @attributes[attribute] end endend
m = MyOpenStruct.newm.honey = 'bee' # => 'bee'm.honey # => 'bee'
Ghost Busting
• Ghost methods aren't really methods
• They won't appear in methods() or respond_to?
• You can fix that last by implementing:
• def respond_to?(method) ... end
• Existing methods will always be called first (source of bugs when names overlap!)
respond_to will help you play nice with other usersYou _could_ override methods() too, but that isn't always necessary
Blank Slate
class BlankSlate instance_methods.each do |m| undef_method m unless m.to_s =~ /^__|method_missing|respond_to?/ end
end
reserved methods (__send__ and __id__)
Blank Slate pattern could be implemented by inheriting directly from BasicObject
BasicObject
• As of 1.9
• Your objects still inherit from Object by default, so use explicitly if needed
[:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__]
Homework
• Play in IRB and get an intuitive feel for Ruby's most basic characteristics
• Ping me when ready for Part 2!