Metaprogramming: A Primerfiles.meetup.com/1138715/metaprogramming_part_1.pdf · Metaprogramming?...

43
Metaprogramming: A Primer Christopher Haupt • @chaupt webvanta.com

Transcript of Metaprogramming: A Primerfiles.meetup.com/1138715/metaprogramming_part_1.pdf · Metaprogramming?...

Metaprogramming:A Primer

Christopher Haupt • @chauptwebvanta.com

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"

Obligatory...

...with great power comes great responsibility...

or...

as I prefer it

...don't cut yourself...

Pre-reqs and Caveats

• MRI 1.9.2

• Things were (are) a little different in 1.8.x

• Non-MRI versions? Trust but verify

Foundation

• Object Model

• Methods

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

class TestClass; endobj1 = TestClass.newobj2 = TestClass.new

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

BasicObject

Kernel

Object

M

C

S

include or proxy classes are inserted into the inheritance chain

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 Methods

• Calling and defining methods on-the-fly

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__]

Concerns?

• Performance

• Debugging

• Clarity

Next Steps...

• Blocks

• Even More Dynamic Class Definition

• Self-writing code

Homework

• Play in IRB and get an intuitive feel for Ruby's most basic characteristics

• Ping me when ready for Part 2!

Thanks

• Art: http://www.plognark.com/blog/1

• Book: "Metaprogramming Ruby" by Paola Perrotta (PragPrag)

• Book: "The Well-Grounded Rubyist" by David Black (Manning)