Sunday, December 18, 2011

Ruby Object Model and Metaprogramming screencasts by Dave Thomas

I bought 6 screencast episodes of Ruby metaprogramming about 3 years ago and only recently have I watched them all. And I must say: it was quite worth it! Just a reminder that it's never too late :) See below for my notes (which have been approved by Dave Thomas himself).



Episode 1 - Objects and Classes
  • Think in terms of object-oriented programming instead of class-oriented programming
  • 2 ways to change self: 
    • by a method call with an explicit receiver
    • by a class or module definition
  • a class does not have a name until you assign it to a constant
  • classes are first-class objects
  • class methods don't exist: they are actually singleton methods on class objects
  • method calling always works the same way. The ruby interpreter:
    • identifies the class of the receiver 
    • looks up the method in that receiver's class
    • if it does not find it, it goes up the hierarchy, and looks up the method in the parent of that class
    • retries the last step until it finds it 


Episode II - Sharing Behavior
  • Object#clone() copies the singleton method too; Object#dup() does not
  • with a prototype, you can clone both the behavior and the state of an object (which is not the case with class-based inheritance)
  • one can subclass from any expression, e.g. class A < (rand > 0.5 ? B : C)
  • do something in the singleton class like so: class << self ; # ... ; end
  • modules have 3 distinct usages:
    • namespace
    • to create module methods
    • to create instance methods
  • module methods can still be modified even if they have been included in a class, by reopening the module
  • a method of an included module will come before a method defined in a superclass, during Ruby's method lookup
  • extend(module_name) actually opens the receiver's singleton class and sticks the content of  module_name in it
  • reason because Person < ActiveRecord::Base is conceptually wrong: the intention is not to build a hierarchy (a "is a" relationship), but rather to share behavior


Episode III - Dynamic Code
  • several ways to create blocks. Worth mentioning:
    • lambda cares about arity (just like a method), proc does not 
    • a "return" inside a proc exits surrounding context
    • a "return" in a lambda exits the lambda
    • a "return" inside a do/end block will exist the surrounding context
    • lambda : like an anonomymous method
    • Proc.new : like inline code
  • a Kernel method is available everywhere in our program
  • eval() takes a string and evaluates it as ruby code
  • a binding encapsulates: 
    • self, 
    • local variables (including method params)
    • any associated block
    • return stack
  • a proc object always has an associated binding
  • nothing prevents you from defining a method within a method
  • define_method() is only available in modules and classes


Episode IV - instance_eval() and class_eval()
  • instance_eval() can be called on any object
  • class_eval() is an alias to module_eval
  • class_eval() can only be called on classes and modules
  • a method definition in class_eval() evaluates in the receiver's class (thus creating instance methods)
  • a method definition in instance_eval() evaluates in the receiver's singleton class (thus creating class methods)
  • "self" in a block of class_eval() or instance_eval() is set to the receiver's
  • include() is a private method
  • you can change the "self" of a block with instance_eval(&block), e.g.
class Robot
  def move(&block)
    instance_eval( &block )
  end

  def up;  puts "u"; end

  def down; puts "d"; end
end

robot = Robot.new
robot.move do
  up   # no need for robot.up()
  down # no need for robot.down()
end



Episode V - Nine examples
  • memoization: @memory[skus] ||= expensive_calculation()
  • interesting examples making use of several metaprogramming techniques seen in previous episodes. If there's one episode to watch, make it that one!


Episode VI - Some hook methods
  • Struct.new returns a subclass of Struct
  • method-related hooks:
    • method_missing
    • method_added
    • singleton_method_added
    • method_removed
    • singleton_method_removed
    • method_undefined
    • singleton_method_undefined
  • Class and Module hooks:
    • inherited
    • append_features
    • included
    • extend_object
    • extended
    • initialize_copy
    • const_missing
  • Marshalling hooks:
    • marshal_dump
    • marshal_load
  • Coercion hooks:
    • coerce
    • induced_from
    • to_xxx , e.g. to_s, to_proc
  • advice: don't forget to call the original method when writin code in the hook method to preserve behavior

1 comment:

Julien said...

You just convinced me to buy them!
Thanks for this post.