Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Ruby meta programming

#!/bin/ruby --verion => 2.0.0-p353

Self

In Ruby, self is a special variable that always references the current object.

  • Inside class or module definition, self refer to the Class or Module object.
  • Inside instance method, self refer to future instance object.
  • Inside class method, self refer to the class.i
  • Inside singleton method, self refer to same instance object.
  • Objects do not store methods, only classes can.

Self is each method's default receiver, if the method cannot be found, then Ruby check ancestors chain until Ruby find that method's definition. We can call super to inherit ancestor method.


Anonymous Class

  • Each object in Ruby has its own anonymous(singleton) class, a class that can have methods, but is only attached to the object itself.
#1. Open singelton class
class Single
end
def Single.show
    puts self
end

#2. attach to singleton class
class Single
    def self.show
        puts self    
    end
end

#3. open singleton class inside class definition
class Single
    class << self 
        def show 
            puts self
        end       
    end
end

#4. instance_eval add instance method for specific instance.
#   instance_eval add class method for default receiver(self) is a class.
class Single
end
Single.instance_eval do 
    def who 
        puts self
    end
end

#5. Assume Single class has defined
#   "<<" means open singelton class for Single class.  
class << Single
  def who
    "Geek"
  end
end

#6.
class Single 
end
Single::class_eval do
	define_method :who
		puts self
  end
end

Single::module_eval do
	define_method :who
		puts self
  end
end

Inheritance Chain

  • Default Module will only appear in Class object:
class P; end
P.new.singleton_class.ancestors
# => [#<Class:#<P:0x000001011a0908>>, P, Object, Kernel, BasicObject] 
P.singleton_class.ancestors
# => [#<Class:P>, #<Class:Object>, #<Class:BasicObject>, 
		Class, Module, Object, Kernel, BasicObject]
  • A customed module is an instance of Module.

  • Class inherits from Module module. A Module module inherit from Object. An Object mixin with Kernel module.

Class.superclass # Module
Module.superclass # Object
  • When you define a class G which is create an instance of the Class G.

  • A class object is NOT an instance of it's superclass. some_class_object.class denotes "instance of", some_class_ object.superclass denotes "inherits from".

  • Included modules are part of inheritance chain, but #superclass ignore modules as super class when called from its subclass.

class G; end
G.superclass # Object
G.class # Class
  • Objects do not store methods, only classes/meta class can.

  • A class isn't really an object. From Ruby's source code:

// https://github.com/ruby/ruby/blob/trunk/include/ruby/ruby.h#L767
struct RObject {
   struct RBasic basic;
   struct st_table *iv_tbl;
 };

 struct RClass {
   struct RBasic basic;
   struct st_table *iv_tbl;
   struct st_table *m_tbl;
   VALUE super;
 };
  • include mixin module's instance methods to that class. Its module locates at before object after object's super class in inheritance chain.

  • extend mixin module's instance methods to singleton class of current object/class. Which become to singleton methods for that class/object. It means the methods can directly be called by that class/object.

  • Module can only call self.methods. It is the same like Class instance B call B.singleton_method_name.

class V; end
class Z; end
module F
  def fg; end
end  

v = V.new
v.extend F
v.singleton_methods
# => [:fg]
v.methods false
# => nil

Z.extend F
Z.singleton_methods
# => [:fg]
  • Both include and extend only add instance methods to the target. Singleton methods in module can be called by lexical lookup.

  • Lookup principle: Go out then up. Start at its singleton class first, then move up. Because BasicObject.singleton_class.superclass is Class, so we can then check Class#instance_methods.

class Z; end
class B < Z; end
module M; end
module V; end

B.include M 	# open B's class and add inst mths to it.                                  
B.extend V  	# open B's singleton class and add inst mths to it.
                                              
Kernel is included in Object.   

meta_class === singleton_class

Ancsetors will never print out singleton_methods and modules under it.

class << B
	# instance method i for singelton class B
	# will be convert ro class method i to class B
	def i; end
	
	# singleton method i for B.singleton_class
	def self.i; end
end

# Instance methods in a singelton class B will be used as class methods
# in class B.



Case 1                        
                              
├── class BasicObj            
├── module Kernel             
├── class Object<Object's instance mth>       
├── class Module              
│  <Module's instance mth>       
├── class Class<Class's instance mth>                

├── module M                  
│ <inject M, but add instance method to B,
│ 		B cannot call those instance mths.>        

│ <ancestors print since here.>       
├── class BasicObject OR meta_class BasicObj      
│   				<Singleton class BasicObj's instance methods><class BasicObject's singleton methods>                  
├── class Object OR meta_class Object
│   			<Singleton class Object's instance methods><class Object's singleton methods>                  
├── class Z OR meta_class Z
│   			<Singleton class Z's instance methods><class Z's singleton methods>                  
├── module V                  
├── class B OR meta_class B
│   			<Singleton class B's instance methods><class B's singleton methods> 
B.mths

B.instance_of? Object # false
B.instance_of? Class # true
B.superclass # Object
B.new.class.ancestors 

# "class Object" is from Class. B.class == Object.class == Class
# Class's super class is Module.

# Append singleton_class.ancestors for traget to know the lookup chain.
# B.singleton_class.ancestors
# [#<Class:B>, V, #<Class:Z>, #<Class:Object>, 
#  #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] 

# Lookup Flow:
# Start at B's singleton class.
# Then look up instance methods in singelton class B,
# and look up singelton methods in class B.
# If method is not found, go up to singleton class B's super class.
# Until hit BasicObject, BasicObject.singleton_class.superclass is Class.
# BasicObject.singleton_class.instance_of? Class is True.
# Then up to instnace methods in :
# 	Class > Module > Object > Kernel > BasicObject.
# If method is still not found, return MethodError. 

Case 2                  
                        
                              
├── class BasicObj            
├── module Kernel             
├── class Object<Object's instance mth>       
├── class Module              
│  <Module's instance mth>       
├── class Class<Class's instance mth> 
├── class BasicObj OR meta_class BasicObj 
│					<Singleton class BasicObject's instance method><BasicObj's singleton mth>
├── module Kernel       
│ <Kernel's instance mth>
├── class Object  OR  meta_class Object<Singleton class Object's instance method>           
│ <Obj's singleton mth>  
│                       
├── class Module  OR  meta_class Module<Singleton class Module's instance method>      
│ <Mod's singleton mth>  
│                                         
├── class Class   OR  meta_class Class<Singleton class Class's instance method> 
│ <Class' singleton mth><print since here.>  
B.class.mths

B.class.singleton_class.ancestors
# [#<Class:Class>, #<Class:Module>, #<Class:Object>, 
#  #<Class:BasicObject>, Class, Module, Object, 
#  Kernel, BasicObject] 

B.class # Class
B.class.superclass # Module
B.class.ancestors       
Class.ancestors 

Case 3  
            
<BasicObj's instance mths>
├── class BasicObj
|		<class BasicObject's instance methods>
├── module Kernel
|		<module Kernel's instance methods>
├── class Object 
|		<class Object's instance methods>
├── class Module
|		<class Module's instance methods>
├── class Class
|		<class Class's instance methods><BasicObject.singleton_class.superclass # => Class># so Case 3 go through class Class inheritance chain.
├── meta_class BasicObject
|		<Singleton class BasicObjec's singleton methods>
├── meta_class Object
|		<Singleton class Object's singleton methods>
├── meta_class Module
|		<Singleton class Module's singleton methods>
├── meta_class Class 
|		<Singleton Class's singleton methods>
├── singelton_class Z
|		<Singleton class Z's singleton methods>
├── module V
| <add V's instance methods to B's singleton class,  
|	but singleton class cannot call those instance methods directly.
|  It needs initialization but singleton class cannot do so.>
├── singelton_class B
|		<Singleton class B's singleton methods>
|
| <print since here.>
B.singleton_class.mths

Z.singleton_class.singleton_class.ancestors
# [#<Class:#<Class:Z>>, #<Class:#<Class:Object>>, 
#  #<Class:#<Class:BasicObject>>, #<Class:Class>, #<Class:Module>, 
#  #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel,
#  BasicObject]

# Start at B.singleton_class's singleton class.
# We found Ruby defautly add #<Class:Class>, #<Class:Module> 
# in look up chain(ancestor).

B.singleton_class.includede_modules # [V, Kernel]
B.singleton_class.instance_methods  # [V's instance methods, ...]

Case 4

├── class BasicObject => instance mths
├── module Kernel     => instance mths     
├── class Object 	  => instance mths
├── class Module      => instance mths
├── class Class       => instance mths
│                          
├── class BasicObject OR singleton_class BasicObject<Singleton class BasicObject's instance method>           
│ <BasicObj's singleton mth>
├── class Object OR singleton_class Object<Singleton class Object's instance method>           
│ <Object's singleton mth>
├── class Z OR singleton_class Z<Singleton class Z's instance method>           
│ <Z's singleton mth>
├── module V 
├── class B OR singelton_class B<Singleton class B's instance method>           
│ <B's singleton mth>		
├── singleton_class of instance B 		=> instance mths
B.new.singleton_class.mths

# check B.new singleton_class's singleton class ancestors(look up chain).                   
B.new.singleton_class.singleton_class.ancestors 
#[#<Class:#<Class:#<B:0x007fb9e99030a8>>>, #<Class:B>, 
# V, #<Class:Z>, #<Class:Object>, #<Class:BasicObject>, 
# Class, Module, Object, Kernel, BasicObject]

Case 5

├── class BasicObject => instance mths
├── module Kernel     => instance mths
│                     
├── class Object      => instance mths
│                     
├── Super class Z     => instance mths
│                     
├── module M          => instance mths
│                     
├── class B   						=> instance mths
├── singleton_class of instance B   => instance mths
B.new.mths

B.new.singleton_class.ancestors
# [#<Class:#<B:0x007fc20d84cc28>>, B, M, Z, Object, Kernel, BasicObject] 

Instance relation:
Class.singleton_class.instance_of? Class        # => true
Module.singleton_class.instance_of? Class       # => true
BasicObject.singleton_class.instance_of? Class  # => true  
Object.singleton_class.instance_of? Class       # => true 
Module.singleton_class.instance_of? Module      # => false 
  • Method lookup: Check target's singleton_class, then up by its ancestors.
  • If the receiver is an instance, then the singleton class will not super singleton Class and singelton Moudle. If the receiver is an class object, then they will be inlcuded in lookup chain.
class A; end
class B < A; end
class C < B; end

A.superclass 
#Object
B.superclass
# A
C.superclass
# B

# class C will first follow ancestor chain which is: 
#   C B A Object, Object inherits from BasicObject
#   (class methods)
# If still cannot found method, then go C.class = Class, C is a instance of Class.
# lookup in Class ineritance chain, lookup all instance methods:
#   Class Moudle Object Kernel BasicObject  
#   (all instance methods) 

Look up Chain.
Method Lookup Flow.

  • module's class method can only be called by lexical lookup.

  • Define instance mehtod in singleton_class of class A will create singleton method for class A.

class G; end  
class << G
  def a; end
end  
G.singleton_methods
# => [:a]
  • Define singleton mehtod in singleton_class of class A will create singleton method for singleton class of A.
class G; end
class << G.singleton_class
  def b; end
end

class << G
  def self.v; end
end

# open G.singlton_class' singleton class.
G.singleton_class.singleton_methods
# => [:b, :v, :nesting, :constants]
  • Define instance mehtod in singleton class of instance a will create singleton method for instance a, instance method for singleton class of instance a.

  • Define singleton mehtod in instance a will only be called by Lexical lookup: a::method_name.

class V; end
v = V.new

def v.s; end
v.singleton_methods
# => [:s]
v.singleton_class.instance_methods
# => [:s]

# << means open some target's singleton class.
class << v.singleton_class
 def g; end
 def self.a; end
end  
v.singleton_class.singleton_methods
# => [:g]

v.singleton_class.singleton_class.singleton_methods
# => [:a, :nesting, :constants]

class << v
  def h; end
  def self.e; end
end

v.singleton_methods
#=> [:s, :e]
v.singleton_class.singleton_methods
# => [:g, :h]

Super, prepend, and delegator

  • Calling super looks for the next method in the method lookup chain.

Callin super from moudle's method.
Delegator

module Blah
 def drive
  super + 'fast'
 end
end

class Vehicle
 def drive
  'driving'
 end
end

class Car < Vehicle
 prepend Blah
end

class Kar < Vehicle
  include Blah
end

class Mar < Vehicle
  extend Blah
end

Car.ancestors
# => [Blah, Car, Vehicle, Object, Kernel, BasicObject] 

Kar.ancestors
# => [Kar, Blah, Vehicle, Object, Kernel, BasicObject] 

Mar.ancestors
# [Mar, Vehicle, Object, Kernel, BasicObject] 

Mar.singleton_class.ancestors
# <Class:Mar>, Blah, #<Class:Vehicle>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

Car.new.drive
# drivingfast
Kar.new.drive
# drivingfast

class F
  def drive 
    "Super E "
  end
end

# Instance object can only extend module to get singleton methods.[for this object use only]
# class object can extend or include to get singleton and instance methods.

f = F.new.extend(Blah)
f.singelton_class.ancestors
# [#<Class:#<F:0x00000103002428>>, Blah, F, Object, Kernel, BasicObject] 
# So when we call f.dirve => lookup chain will make Blah's drive call first.
# then super works to find F.

f.drive
# Super E fast
  • An instance object can only extend some module: which get to instance methods: reason: extend define method as def self.method instead of def method. so you will only can use self.method.

  • Consider main object, self suppose should not have :include methods. but why it works? Because method_missing may direct include to its super class's method. see code below.

self.respond_to? :include
self.respond_to? :extend

module Foo; def foo; end; end
module Zoo; def zoo; end; end

include Foo
# Open Object class and set instance method for it.
respond_to? :foo    # true

extend Zoo
# Open main object and set self.zoo for it.
resond_to? :zoo     # true

# So this means main object should have method_missing define as follows:
def method_missing mth, *args
  self.send mth.to_sym, *args
end
# This let include can work for main object.
# include or extend always add a module after current class but before Super class.
# prepend can add before current object.
  • Delegator:
class Delegatte
  def initialize(obj)
    @obj = obj
    @obj.class.instance_methods(false).each do |m|
      # For BowlerHatDecorator.new, receiver is BowlerHatDecorator. 
      # And self is BowlerHatDecorator.
      # Though in method lookup chain will traverse othe class,
      # self still set to BowlerHatDecorator.
      self.class.superclass.send(:define_method, m) do |*args|
        @obj.send(m, *args)
      end
    end
  end

  def describee
    super
    puts "I am describer"
  end
end

class SimpleDelegattor < Delegatte
  def initialize(obj)
    # instance variable @obj will be created by super
    super
  end
end

class Character
  def describe
    puts "You are a dashing, rugged adventurer."
  end
  
  def describee
    puts "You are a dashing, rugged adventurer."
  end
end

class BowlerHatDecorator < SimpleDelegattor
  def describe
    super
    puts "A jaunty bowler cap sits atop your head."
  end
end 

SimpleDelgator.instance_methods
# [:nil?, :===, :=~, :!~, :eql?, :hash, ...]

cohen = BowlerHatDecorator.new(Character.new)

# Now has define a describe method.
SimpleDelgator.instance_methods
# [:describe, :nil?, :===, :=~, :!~, :eql?, :hash, ...]

cohen.describe
# => You are a dashing, rugged adventurer.
# A jaunty bowler cap sits atop your head.

p cohen.singleton_class.ancestors
# [#<Class:#<BowlerHatDecorator:0x0000010211ee70>>, 
#    BowlerHatDecorator, SimpleDelegattor, Delegatte, Object, 
#    Kernel, BasicObject]

del = Delegatte.new(Character.new)
del.describee
# You are a dashing, rugged adventurer.
# I am describer

Block

# example from:
# http://ruby-metaprogramming.rubylearning.com/html/ruby_metaprogramming_3.html
def who
  person = "Matz"
  yield("rocks")
end
person = "Matsumoto"
who do |y|
  puts("#{person}, #{y} the world") # => Matsumoto, rocks the world
  city = "Tokyo"
end
# puts city # => undefined local variable or method 'city' for main:Object (NameError)
def mem_result(klass, method)
  mem = {}
  Class.new(klass) do
    define_method(method) do |*args|
      if mem.has_key?(args)
        mem[args]
      else
        mem[args] = super
      end
    end
  end
end
  • When you define a block, it simply grabs the bindings that are there at that moment, then it carries those bindings along when you pass the block into a method.

  • Observe that the code in the block sees the person variable that was around when the block was defined, not the method's person variable. Hence a block captures the local bindings and carries them along with it. You can also define additional bindings inside the block, but they disappear after the block ends.

  • Passing lambda or ->() as block parameter will still be a lambda for return.

  • return in lambda will treat as method and return back to current method scope. return in block will return the result for current method. Use break, continue, next insted.

  • For procs created using lambda or ->() an error is generated if the wrong number of parameters are passed to a Proc with multiple parameters. For procs created using Proc.new or Kernel.proc, extra parameters are silently discarded.

Difference between proc, lambda, and block.

class G
  def lambda_literal
    [1,2,3,4].each(&->(x) { return false})
    true
  end

  def lambda_test
    [1,2,3,4].each(&lambda { |c| return false})
    true
  end

  def block_test
    [1,2,3,4].each do |i|
      return false
    end
    true
  end

  def proc_test
    [1,2,3,4].each(&Proc.new do 
      return false
    end)
    true
  end

  def check &block
    p block
  end
end

G.new.lambda_test
# true
G.new.lambda_literal
# true
G.new.block_test
# false
G.new.proc_test
# false

G.new.check &->() {}
# <Proc:0x0000010309b100@(irb):31 (lambda)>
# return => #<Proc:0x0000010309b100@(irb):31 (lambda)> Still a lambda

G.new.check &lambda {}
# <Proc:0x00000103092730@(irb):32 (lambda)>
# return => #<Proc:0x00000103092730@(irb):32 (lambda)> Still a lambda

G.new.check do; end
# <Proc:0x00000103089fe0@(irb):33> is a Proc
# return => #<Proc:0x00000103089fe0@(irb):33> 

G.new.check &(Proc.new do; end)
# <Proc:0x0000010306b7e8@(irb):35>
# return => #<Proc:0x0000010306b7e8@(irb):35> 

class B
 def self.b &block
   block.call
 end
end

B.b &->{ p "hi"}
# "hi"

Methods for Metaprogramming

  • When Ruby does a method look-up and can't find a particular method, it calls a method named method_missing() on the original receiver. The BasicObject#method_missing() responds by raising a NoMethodError.

  • The methods class_variable_get (this takes a symbol argument representing the variable name and it returns the variable’s value) and class_variable_set (this takes a symbol argument representing a variable name and a second argument which is the value to be assigned to the variable) can be used.

  • Use the class_variables method to obtain a list of class variables.

  • Use the instance_variable_get and instance_variable_set to obtain list of instance variables. const_get with symbols and const_set to get constants

  • Entities like local variables, instance variables, self. . . are basically names bound to objects. We call them bindings.

  • eval, module_eval, and class_eval are operate on Class or Module rather than instance. Instance which is a specific object as well can call instance_eval to operate executoins and call instance variables.

  • instance_eval can also be used to add class methods in class object. class_eval able to define an instance/singleton method in class or module, instance_eval create instance or class methods depeonds on its receiver is either a instance or class.

  • The module_eval and class_eval methods can be used to add and retrieve the values of class variables from outside a class.

  • The Module#define_method() is a private instance method of the class Module. The define_method is only defined on classes and modules. The defined method then can be called by the instance.

  • Leveraging by respond_to?(), class(), instance_methods(), instance_variables(), we can check object information at run-time.

  • You can call any methods with send(), including private methods. Use public_send() to call oublic methods only.

  • The Module#define_method() is a private instance method of the class Module. The define_method is only defined on classes and modules. You can dynamically define an instance method in the receiver with define_method( ). You just need to provide a method name and a block, which becomes the method body

  • To remove existing methods, use the remove_method within the scope of a given class. If a method with the same name is defined for an ancestor of that class, the ancestor class method is not removed. Alternatively, undef_method prevent specific class call that method even though the ancestor has the same method.


Open Singleton Class and New scope

# 1.

# First open the class H.
# Thorugh << to set self as singleton class.
# Becasue it is in singleton class scope so it creates new scope.
# << will set self to singleton class, and open it.
x = 5
class H
    # open H's singleton class
    class << self
        p x
        p self.name
    end
end

# directly open singleton class, set self to singleton class; 
# create new scope because its in singleton class scope.
class << H
    p x
    p self.name  # <Class:H> which is singleton class.
    
    # define instance methods, for class H, which is H's singleton methods.
    def h 
    	p self   # H.h => because reciver self is set to H, so will return H,
    		 # rather than <Class:H>.
    end
    
    # define instance method in H.singleton_class' singleton class.
    # which is H.singleton_class' singleton method
    def self.g   # H.singleton_class.g
      p self     # return <Class:H>.
    end
end

# 2.

# This open class H, not in singleton class, so not create new scope.
# It is able to use local variable x.
# defines method in class H, but not in singleton class.
H.class_eval { p x; p self.name }

# instance_eval breaks apart the self into two parts.
# - the self that is used to execute methods 
# - the self that is used when new methods are defined. 

# When instance_eval is used:
# - new methods are defined on the singleton class.

# - but the self is always set the object itself. [Either class or instance]
#    	-> No matter inside or outside of new methods block.

# while method defined in singleton class,
# 	-> Ruby let it able to access outside local variable x
# 		-> Because it sets self as receiver's class.

Person.instance_eval do
  def species
    "Homo Sapien"
  end
 
  self.name #=> "Person"
end

# 3. 

# This open singleton class for H class, will create new scope.
# The self will still assoicate to 'H' because for method s, H is the recevier.
# And because << is not used in here which will set self to singleton class
# So it is not able to call local variable x in a singleton method.
def H.s
	p x          
  p self.name
end

# same as 3, here we still set self as H, then define s method in H's singleton class.
# so not able to use local variable outside.

class H
	def self.s
  	p x
    p self.name
  end
end
mechanism method resolution method definition new scope?
class Person Person Person yes
class << Person Person’s metaclass Person’s metaclass yes
Person.class_eval Person Person no
Person.instance_eval Person Person’s metaclass no

define new scope are not able to access outside local variable

  • In ruby 1.8.7 case 1 cannot call class_variable_get to get class variables.

  • In ruby 2.0.0 case 1 can call class_variable_get to get class variables though it is in the new scope.

  • << not only open snigleton class, also set current self to self's singleton class.

  • Ruby is used to denote a singleton class: #<Class:#String:...>. ex: #Class:H

  • Each instance object have its own singleton class.

  • class_eval open object class, and deirectly add method definition for it, so we can add def self.method for add class mehthods.

  • instance_eval will set self to receiver, and evaluate all methods in the block as singleton methods. If receiver is a class, then it define new singleton method in singleton class. If receiver is an instance, then it deine new instance method in instance's singleton class.

  • Singleton class holds instance methods, but when it attached to an object which either class or instance, those instance methods convert to singleton methods for that class or instance.

  • You can’t create a new instance of a singleton class.

  • methods only defined in class or singleton classs, not instance object.

http://yehudakatz.com/2009/11/15/metaprogramming-in-ruby-its-all-about-the-self


Constant and instance_eval

module One
	CONST = "Defined in One" 
  def self.eval_block(&block)
    instance_eval(&block)
	end 
end

module Two
	CONST = "Defined in Two" 
  def self.call_eval_block
		One.eval_block do 
    	CONST
		end 
  end
end

Two.call_eval_block # => "Defined in Two" in 1.9.2 and 1.8.7 and later
Two.call_eval_block # => "Defined in One" in 1.9.0
  • In Ruby 1.8.7 and 1.9.2, calling constant in instance_eval will get the constant in the lexical scope which refered to.

  • In Ruby 1.9.0 calling contant in instance_eval will get the constant in the receiver's class.

  • instance_eval open receiver's singleton class and bind each method's receiver if it is not specified(instance method, or so-called UnboundMethod). So methods all are binded.

  • class_eval open class and define it as usual. Methods without explicitly receiver are UnboundMethod.

class T; end

T.class_eval do
  def um; end
end

x = T.instance_method :um
# <UnboundMethod: T#um> 

# To bind unbound method :um
t = T.new
x.bind t
# <Method: T#um>

# instance_eval automatically bind methods to its receiver.
T.instance_eval do
    def m; end
end

s = T.method :m
# <Method: T.m> 
  • In block of instance_eval, self be set and hijack to current receiver. Need caution for DSL.

  • class definition changes the default definee but method definition does not.

  • When you give a receiver to a method definition, the method will be adde into the eigenclass of the receiver.

  • if you define a method with the normal method definition syntax, the default definee will have the method as an instance method.

  • class_eval and instance_eval:

    self default definee
    class_eval the receiver the receiver
    instance_eval the receiver eigenclass of the receiver

    Must read: Three implicit contexts in Ruby.
    DSL instance eval.
    instance_eval change self.

  • class_eval and instance_eval can accept string as code, both sets self to receiver during evaluation. We can use Here doc or string to make codes rather than use block. When we call class_eval and instance_eval with block, self will be set to receiver inside the block. And for instnace_eval the method definee will bind to receiver from unbound method to bound method.

module Migration
  class Base
    def self.create_table name, &block
      t = Table.new(name)
      t.evaluate &block  
      t.create
    end
  end
end

class Table
  attr_reader :name, :columns
  def initialize(name)
    @name = name.to_s
    @columns = []
  end

  def evaluate &block
    instance_eval &block
  end

  def string(*columns)
    @columns += columns
  end

  def create
    puts "creating the #{@name}, with columns #{columns.inspect}"
  end
end

class Mardel < Migration::Base
  def self.change
    create_table :table do
      string name("c1"), name("c2")
    end
  end

  def self.set_name(name)
    "#{name}_column"
  end
end

Mardel.change
# ArgumentError: wrong number of arguments (1 for 0)
# This is cause error because, self has set to Table due to instance_eval.
# So `name("c1")` will get error because attr_reader :name not accept any argument.
# We make changes as below:

class Mardel < Migration::Base
  def self.change
    create_table :table do
      string set_name("c1"), set_name("c2")
    end
  end

  def self.set_name(name)
    "#{name}_column"
  end
end

Mardel.change
# NoMethodError: undefined method `set_name' for #<Table:0x0000010184c928>
# In here, we need passing block's binding, so can call Mardel.set_name method.
# Fix as below:

class Table
  def method_missing(method, *args, &block)
    @self_in_block.send(method, *args, &block)
  end

  def evaluate &block
    @self_in_block = eval "self", block.binding
    instance_eval &block
  end
end

Mardel.change
# creating the table, with columns ["c1_column", "c2_column"]
# now works.
  • class_eval only works on class object, which open that class and evaluate it. Not prepend receiver for it.
  • eval evalute string as expression. ex: eval "'p' + 'pp'"

def vs define_method

  • Inside the block of clas_eval or instance_eval, don't directly make method definition, instead, use define_method to let closure works, which will enable to use variables outside of block. This is because define_method is not a keyword, so method definition will be resolve at runtime. If we use def, method definition will be directly scanned in lexical parsing. The same idea as alias v.s. alias_method.
class A; end

class B
  def b
    x = 10
    A.class_eval do 
      # x can be used inside block due to closure feature.
      p x
      # def is a keyword, the method definition will be parsed in lexical parsing
      # so x is actually some local var. 
      def g
        x
      end
    end

    # define_method not a key word.
    # so variable x will be parse until run time.
    A.class_eval do 
      define_method :z do
        p self
        x
      end
    end
  end
end

A.new.z
# => 10

A.new.g
# NameError: undefined local variable or method `x' for #<A:0x0000010126d408>

Binding

  • Methods in Ruby are not objects, but Method class can represent the given method. Methods are bind with symbol, so we can provide a symbol to send which calls the method bind with its name.
# First call to eval, the context is binding to the main object
#	-> and call local variable str.
# Second call, the context moves inside the getBinding method.
# And the local value of str is now that of the 
# 	-> str argument or variable within that method. 
class MyClass
   @@x = " x"
   def initialize(s)
      @mystr = s
   end
   def getBinding
      return binding()
   end
end
class MyOtherClass
   @@x = " y"
   def initialize(s)
      @mystr = s
   end
   def getBinding
      return binding()
   end
end

@mystr = self.inspect
@@x = " some other value"
ob1 = MyClass.new("ob1 string")
ob2 = MyClass.new("ob2 string")
ob3 = MyOtherClass.new("ob3 string")

puts(eval("@mystr << @@x", ob1.getBinding)) 
puts(eval("@mystr << @@x", ob2.getBinding)) 
puts(eval("@mystr << @@x", ob3.getBinding)) 
puts(eval("@mystr << @@x", binding))

# In 1.9 output:
# ob1 string some other value
# ob2 string some other value
# ob3 string some other value
# main some other value

# In Ruby 1.9 does evaluate class variables within a binding. 
# However, it gives preference to class variables.
# if they exist, in the current binding(main object). 
# This is differ than 1.8, which is always binding context to receiver's context.
  • use Kernel::eval to evalute string expression with binding in main scope.

  • Local and instance variables can captured by calling binding. You can access any local and instance variables by passing a reference of a binding context and calling eval on it.

Binding

@x = 40

class B
  def initialize
    @x = 20
  end

  def get_binding
    binding
  end
end

class A
  attr_accessor :x
  def initialize
    @x = 99
  end

  def a b
    s = eval "@x", b
    puts "A's @x: #{@x}"
    puts "Binding's @x: #{s}"
    @x = s
    puts @x
    @x
  end

  def ev b, binding
    eval "a #{b}", binding
  end
end

obj = A.new
b = B.new

# use Kernel::eval, which is in main scope 
# send context, variables to obj

# obj.ev "binding", binding
# NoMethodError: undefined method `a' for main:Object.
# It is because obj.ev binding to main scope, but main scope dont have method a.

def a b
  puts 'in Main scope'
end

obj.ev "binding", binding
# in Main scope

obj.a binding
# A's @x: 99
# Binding's @x: 40
# 40
# => 40 

obj.a b.get_binding
# A's @x: 40
# Binding's @x: 20
# 20
# => 20 

http://www.hokstad.com/ruby-object-model


Send

# An block cannot exist alone, it needs a method to be attached with it. 
# We can convert a block to Proc object.
# a = Proc.new{|x| x = x*10; puts(x) }
# b = lambda{|x| x = x*10; puts(x) }
# c = proc{|x| x.capitalize! }

# send does not take block as params.
def method_missing( methodname, *args ) 
	self.class.send( :define_method, methodname,
		# => A Proc object pass into send *args array
	  	lambda{ |*args| puts( args.inspect) } 
  )
end

Extend and Include

  • The extend method will mix a module’s instance methods at the class level. The instance method defined in the Math module can be used as a class/static method.

  • Module's class methods can only be call by constant lookup implicitly or explicitly. Some object include a module can change object to module's namespace, then can call the class method through the constant lookup by object.module_eval.

module A
  module B
    def b; end
    def self.bb; end
  end
end
 
class G
  include A
end
 
G::B
# => A::B, Object G change to namespace A and do constant Lookup.

class V
  extend A
end
 
V.singleton_class::B
# => A::B  
  • The include method will mix a module’s methods at the instance level, meaning that the methods will become instance methods.

  • The Module.append_features on each parameter for extend or include in reverse order.

  • If we include multiple module in one line, it will look up by its declaring order. If we include or extend in multiple times, then it will look up from the reverse declaring order.

module A
  def say; puts "In A"; end
end

module B
  def say; puts "In B"; end
end

class Parent
  def say; puts "In Parent"; end
end

class Child
 include A, B
end

p Child.ancestors
# [Child, A, B, Object, Kernel, BasicObject]

Child.new.say
# In A

class ChildToo
  include A
  include B
end

p ChildToo.ancestors
# [ChildToo, B, A, Object, Kernel, BasicObject]

ChildToo.new.say
# In B
  • The difference is that include will add the included class to the ancestors of the including class, whereas extend will add the extended class to the ancestors of the extending classes' singleton class.

  • Recall that for each instance inherits from Object, Object provides a extend method which add instance method from the module. It is different as declare extend in class definition, which add methods to object's singleton class.

    http://juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby/

  • extend a module A in another module B will add A to singleton class of B, include a module A in module B will add A to B's class.

  • class method defined in module is prepare for receiver's singleton class to use, instance method is prepare for receiver's instance.

  • Class.singleton_class.ancestors to track inheritance chain from singleton class.

  • Module cannot be instantiate with new for itself.

# Namespace will transfer to module's namespace.

module A
  module B
    module C
      def a(val = nil)
        !val ? super val : puts "IN C"
      end
    end

    def a(val = nil)
      puts "IN B"
    end 
  end

  module D
    def self.d
      puts "IN D"
    end
  end
end  

module A
  include B
  extend D	
  # D will not be found in ancestors output, but in singleton class' chain.
end

class G 
  include A
end

class T
  extend A
end

T.singleton_class.ancestors	
# [#<Class:T>, A, A::B, #<Class:Object>, 
#  #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

T.singleton_class.included_modules
# [A, A::B, Kernel]

T.singleton_class::D
# A::D
# Because ruby cannot find namespace in T class, so go module A to find namespace D.

T.singleton_class::D.d
# IN D
# now call singleton method d in moudle D. 
# We cannot call instance method here, because receiver is a class.

G.ancestors			
# [G, A, A::B, Object, Kernel, BasicObject] 

# Include or extend will not transfer namespace to for host module.
# For class or singleton_class, it will check include or extended module namespace for finding methods.

G::B
# A::B
# Because cannot find namespace in G, so go to namespace A then go module B and fount it.

G::C
# A::B::C, ruby go deeper to find namespace C under module B.

G::D.d
# In D

G::B.a
# NoMethodError

G.new.a("str")
# In B
# because we not include C in A, so it will only call B::a

module A
  include C
end

class G
  include A
end

G.include A

# Now we add a new include in module A.
# But class G will load previous module A's included modules.
# So we need to reopen the class G and include module A for reloading again.
# Or just one line G.include A for same thing above.

G.ancestors
# [G, A, A::B::C, A::B, Object, Kernel, BasicObject] 

G.new.a("str")
# In C
G.new.a
# In B

# finally check moudle A's namspaces.

A.included_modules
# [A::B::C, A::B] 
A.singleton_class.included_modules
# [A::D, Kernel] 

module W
  class O
    def o
      puts "OO"
    end
    
    def self.o
      puts "Singleton OO"
    end
  end
end

# class dont have to include in module. Just namespace for it.

class L 
  include W
end

L::O
# W::O

L::O.new.o
# "OO"

L::O.o
# "Singleton OO"
  • In module, define class method such self.method will be in module level methods. Which only be access in that namespace explicitly or implicitly.

  • Constant lookup for module level method.

  • Include module make instance method in module become instance method in that included class.

  • Extend module make instance method in module become class method in that extended class.

  • Class method in module is only call by the way of constant lookup.

  • So in module A extend B, use extend to get instance methods from B to A's class methods, which are the same as self.instance_methods_of_b, B's class methods still need constant lookup to call.

module G
  def g; end
  def self.gg; end
end

class A
  extend G
end

A.singleton_methods
# have :g

A.include G
A.instance_methods
# have :g

module H
  extend G
end
# because extend send instance method of G to H's singleton class.

H.singleton_methods.include? :g
# true
# rails c
module A
  module S
    extend ActiveSupport::Concern
    included do
      def self.n
        @@n
      end

      def self.n= (str)
        @@n = str
      end
    end
  end

  module D
    def d
      puts "IN D"
      puts "name: #{n}"
    end
  end

  module Z
    def pi; end

    # This made :ty in A::Z namespace only.
    def self.ty; end
  end

  module ZZ
    def pizz; end

    # This made :ty in A::ZZ namespace only.
    def self.tyzz; end
  end
end  

module A
  extend D
  include S
  include Z
  extend ZZ
end

A.ancestors
# => [A, A::Z, A::S] 

A.singleton_class.ancestors
# => [#<Class:A>, A::ZZ, A::D, Module, 
    ActiveSupport::Dependencies::ModuleConstMissing, 
    Object, PP::ObjectMixin, ActiveSupport::Dependencies::Loadable, 
    JSON::Ext::Generator::GeneratorMethods::Object, Kernel, BasicObject] 

A::Z.ty
# => nil

A::ZZ.tyzz
# => nil

# self.method in module will make that method only in that namespace scope.
# Instace method will only be used in instance objec only. 
# Include instance method will be usable. Because it include in that instance class.
# Extend instance method have no meaning in that instance. 
# Because instance method is in singleton_class but cannot be called.

A::ZZ.instance_methods
# => [:pizz] 

class T
  include A
end

T.new.singleton_class::Z
# => A::Z
T.new.singleton_class::Z.instance_methods
# => [:pi] 
T.new.singleton_class::ZZ.instance_methods
# => [:pizz] 

T.module_eval do 
  A::ZZ.tyzz
end
# tyzz existed for module level namespace A::ZZ

T.module_eval do 
  A::Z.ty
end
# ty existed for module level namespace A::Z

T.module_eval do 
  # A::Z.pi
  # no-method error
end


A.n = "str"
A.d
# IN D
# name: str

A.respond_to? :pizz
# true

# Conclude this: 
# module A extend module B
#   B's instance method will be treat as A's method
#   B's class method will be treat as A::B's method
#
# module A include module B
#   B's instance method will be in class T which include or extend A's method
#     if class T include A => B's instance method in T.new.methods
#     if class T extend A  => B's instance method in T.new.singleton_class.methods
#   B's class method will be treat as A::B's method
#     if class T include A => B's class method in T.new.singleton_class::B
#     if class T extend A => B's class method cannot be called directly. 
#	(danger way: T.singleton_class::A::B) or use module_eval

T.module_eval do 
  A::ZZ.tyzz
end

Exercise

# class MaskedString < String
#   def tr_vowel
#     tr 'aeiou', '*'
#   end
#   def self.tr_vowel str
#     str.tr 'aeiou', '*'
#   end
# end
# Define the following class without class MaskedString, 
#	-> def tr_vowel and def self.tr_vowel.
# Hint: 
# instance_eval, class_eval, define_method, module, 
# 	-> extend, extended, include, included.

# Naming as constant, so the anonymous class: Class.new will be given a name.
ClassObject = Class.new(String)

module AddClassMethods
  self.instance_eval do
    define_method :tr_vowel do |str|
      str.tr 'aeiou', '*'
    end
  end  
end

ClassObject.class_eval do 
  define_method :tr_vowel do 
      tr 'aeiou', '*'
  end

  # instance method
  def lets 
    puts 'lets'
  end

  # singleton method
  def self.lets 
    puts 'self.lets'
  end
end

ClassObject.instance_eval do 
  extend AddClassMethods
  define_singleton_method :as do 
    puts "as"
  end

  # singleton method
  # def less === def self.less
  def less
    puts 'less'
  end
end

puts ClassObject.tr_vowel("America")
puts ClassObject.new("ByMyWill").tr_vowel

DSL and yield

  • yield expect method carry a block and transfer evaluation from method to that block. By default method's argument list don't have to list block argument, but provide it explicitly with & in argument list will convert that block to Proc object. It is more readable and avoid block chain pass arguments which may affect performance.

  • Although the method did not explicitly ask for the block in its arguments list, the yield can call the block. This can be implemented in a more explicit way using a Proc argument.

  • Whenever a block is appended to a method call, Ruby automatically implicitly converts it to a Proc object but one without an explicit name. The method, however, has a way to access this Proc, by means of the yield statement.

  • If & provides in a argument, the block attached to this method is explicilty converted to a Proc object and gets assigned to that last argument by its argument name.

# bad
def with_tmp_dir
  Dir.mktmpdir do |tmp_dir|
    Dir.chdir(tmp_dir) { |dir| yield dir }  # this block just passes arguments to yield
  end
end

# good
def with_tmp_dir(&block)
  Dir.mktmpdir do |tmp_dir|
    Dir.chdir(tmp_dir, &block)
  end
end

with_tmp_dir do |dir|
  puts "dir is accessible as parameter and pwd is set: #{dir}"
end

Consider using explicit block argument to avoid writing block literal that just passes its arguments to another block. Beware of the performance impact, though, as the block gets converted to a Proc.

https://github.com/bbatsov/ruby-style-guide

http://rubylearning.com/blog/2010/11/30/how-do-i-build-dsls-with-yield-and-instance_eval/

http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Method_Calls#Blocks


method_missing and defind_method

Method missing.
Ruby’s define_method, method_missing, and instance_eval.

class A
 def method_missing(name, *args, &block)
   if name ~= /^ss$/
     puts "Name: #{name}"
     puts "Args: #{args}"
     puts "Block: #{block}" if block_given?
   else
     super
   end
 end
end

A.new.ss(1, 2, 10) { |x| puts x }
# Name: ss
# Args: [1, 2, 10]
# Block: <#<Proc:0x0000010215ed68@(irb)>

x = A.new

class << x
 define_mehthod("mth") { |args = nil| args ? puts "#{args}" : puts "No args input." }
 define_mehthod(:sym_mth) { |*args| puts "Args: #{args}" }
end

x.mth("args in here")
# args in here

x.mth
# No args input

x.sym_mth(1, 2, 3)
# Args: [1, 2, 3]

x.sym_mth
# Args:

Fiber

  • Use fiber to mock Enumerator's iterator.

  • Fiber[mandarin]

  • Fiber requires a block. Inside block, put Fiber class object to use its class method.

  • Arguments passed to resume will be the value of the Fiber.yield expression or will be passed as block parameters to the fiber’s block if this is the first resume.

  • Alternatively, when resume is called it evaluates to the arguments passed to the next Fiber.yield statement inside the fiber’s block, or to the block value if it runs to completion without hit any further Fiber.yield.

  • Due to above statment, fiber last return result is the same as argument value passed to resume.

  • Fiber can store context, block switch to new context every time.

class Enum
  def initialize
    @yielder = Fiber.new do
      yield Fiber
    end
  end

  def next
    @yielder.resume
  end

  def mock_yield
    next
  end
end

e = Enum.new do |yielder|
  num = 1
  loop do
    # Fiber.yield
    yielder.yield num
    num += 1
  end
end

p e.mock_yield	# 1
p e.mock_yield	# 2
p e.mock_yield	# 3

f = Fiber.new do |arg|
  Fiber.yield arg + 5, arg + 6
end

f.resume 5
# [10, 11]
f.resume 5
# 5
f.resume 5
# fiber dead

require 'fiber'
f.alive?
# false

f = Fiber.new do |arg|
  p 1
  Fiber.yield arg + 5
  p 2
  Fiber.yield
  p 3
end

# first resume run until hit Fiber.yield or block end.
# if hit block yield, return its control to f's scope.
f.resume 4
# 1
# => 9 

f.resume 4
# 2
# => nil 

# f.resume transfer control to Fiber block, finish print 3, hit the block end
f.resume 4
# 3
# => 3 

z = Fiber.new do |arg|
  Fiber.yield arg + 5
end

z.reusme 4
# => 9
z.resume 4
# => 4
# Because resume turns control back to Fiber block, but does not hit any Fiber.yield in next expressions,
# It returns reusme's argument value as block's return value.

Ruby Method Naming

  • Ruby allow letters, underscore, and numbers not in first letter, end with =, ?, ! for naming conventions.
  • It also support a syntax sugar for [] and []= which is [](id), [](key, value) for hash or array liked method.
  • And many operators can redeine in your class, but you would not able to process original operators if it support from superclass.
class MockHash
  def initialize(input)
    @hash = Hash(input)
  end

  def [](id)
    @hash[id]
  end

  def []=(id, value)
    @hash[id] = value
  end
end

class MockHashII
  def initialize(input)
    @hash = Hash(input)
  end

  def to_h
    @hash
  end

  def method_missing(mth, *args)
    to_h.public_send(mth, *args)
  end
end

class MockArray
  def initialize(input)
    @ary = Array(input)
  end

  def [](id)
    @ary[id]
  end

  def []=(id, value)
    @ary[id] = value
  end

  def to_a
    puts "Loosely conversion for explicitly cal."
    @ary
  end

  def to_ary
    puts "Strct conversion for implicitly call."
    @ary
  end

  def <<(values)
    @ary << values
  end
end

ary = MockArray.new [1,2,3,4,5,6]
hash = MockHash.new a: 1, b: 2, c: 3
hash2 = MockHashII.new z: 1, b: 2, c: 3

hash[:a]
# => 1

hash[:z] = 8
# => 8

ary[0]
# => 1

ary[6] = 9
#=> 9  

hash2[:v] = 8
# => 8

[] + ary
# Strct conversion for implicitly call.
# => [1, 2, 3, 4, 5, 6, 9]

a = *ary
# Loosely conversion for explicitly cal.
# => [1, 2, 3, 4, 5, 6, 9]
  • Ruby core class never use explicit conversions if not explicit conversion calls. But it have counter example. Check Confident Ruby book page 58 for more info.

Counter example :

  • When we use string interpolation, Ruby implicitly uses #to_s to convert arbitrary objects to strings.
"Time class use explicit conversion: #{Time.now}"
# string interploation conver Time object by Time#to_s method

class B
  def to_s
    "B"
  end
end

"" + B.new
# TypeError: no implicit conversion of B into String

String B.new
# "B"
# call `to_s` instead

Explict or implicit conversion methods
Method naming
Ruby conversion protocols.

  • Use implicit conversion if you want to guard the input type such as nil value. If you dont care about the edge case of input, just want to run the logic, use explicitly conversion instead.
nil.to_s
# convert the result "" and kepp logic running
nil.to_str
# NoMethodError: undefined method `to_str' for nil:NilClass

Rack middle ware call chain

class A
  def initialize(app = nil)
    @app = app
  end

  def call(env)
    p env
    p "A"
  end
end

class B
  def initialize(app)
    @app = app
  end

  def call(env)
    p env
    p "B"
    @app.call(env)
  end
end

class C
  def initialize(app)
    @app = app
  end

  def call(env)
    p env
    p "C"
    @app.call(env)
  end
end

# simulate Rack::Builder call chain
app_a = A.new
stack = [Proc.new{ |app| B.new app}, Proc.new{ |app| C.new app}]
stack = stack.reverse.inject(app_a) { |e, a| e[a] }
# (Proc.new{ |app| B.new app }.call(Proc.new{ |app| C.new app}))
# #<B:0x000001030bff00 @app=#<C:0x000001030bff28 @app=#<A:0x000001030bff50 @app=nil>>>
# return B object

stack.call 5
# will go B.call() method, if it calls @app.call, then will pass to C.call and so on.
# In here B.call is object method, not Proc's call().

Equality

  • If we override #eql? method then must override #hash? in class for Hash key comparison. Otherwise ruby will back to its default implementation for #hash in Object class.
  • Each hash insertion generate hash values first, then compare whether two keys is duplicate(#eql? return true) repeatedly. eql? and hash are used when insertion to some hash-based data strucutre(hash, set).
  • Set/Hash #include? method will get hash values first, check whether its obecjt_id and hash value exist in the set/hash. It shortcircuit to return true if it can find the entry, o.w. call eql? method to determine. Check the EQUAL(table,x,y) function in source code to prove this.
// http://rxr.whitequark.org/mri/source/st.c#396
static inline st_index_t
find_packed_index(st_table *table, st_index_t hash_val, st_data_t key)
{
    st_index_t i = 0;
    while (i < table->real_entries &&
    	// if hash_val exists and key is exist in table(use ==), break loop. 
           (PHASH(table, i) != hash_val || !EQUAL(table, key, PKEY(table, i)))) {
        i++;
    }
    return i;
}

#define collision_check 0

int
st_lookup(st_table *table, register st_data_t key, st_data_t *value)
{
    st_index_t hash_val;
    register st_table_entry *ptr;

    hash_val = do_hash(key, table);

    if (table->entries_packed) {
        st_index_t i = find_packed_index(table, hash_val, key);
        if (i < table->real_entries) {
            if (value != 0) *value = PVAL(table, i);
            return 1;
        }
        return 0;
    }

    ptr = find_entry(table, key, hash_val, hash_val % table->num_bins);

    if (ptr == 0) {
        return 0;
    }
    else {
        if (value != 0) *value = ptr->record;
        return 1;
    }
}

// EQUAL(table,x,y) function 
// http://rxr.whitequark.org/mri/source/st.c#085

#define EQUAL(table,x,y) ((x)==(y) || (*(table)->type->compare)((x),(y)) == 0)
# Only override #eql?, Hash insertion will call Object#hash for key's hash value.
class B
  def eql?(c)
    true
  end
end

{B.new => 5, B.new => 7}
# Two entries
# {#<B:0x007f8d1c807680>=>5, #<B:0x007f8d1c807658>=>7} 

# Only override #hash
# Hash insertion will call Object#eql? for key's hash value comparison.
# But Object#eql? will also compare object_id by default implementation.
class T
  def hash
    p "T's hash"
    0
  end
end

{T.new => 5, T.new => 7}
# "T's hash"
# "T's hash"
# => {#<T:0x007f8d1d86f068>=>5, #<T:0x007f8d1d86f040>=>7}

class V
  def hash
    p "V's hash #{self.object_id}"
    0
  end
  
  def eql?(c)
    p "V's eql?"
    self.hash == c.hash
  end
end

k = V.new
# k.object_id 70122022664000
b = V.new
# b.object_id 70122022646420

# each hash insertion generate hash values first, 
# then compare whether two key is duplicate.

{ k => 5}
# "V's hash 70122022664000"
# => {#<V:0x007f8d1c80ee80>=>5} 

{ k => 5, b => 7}
# "V's hash 70122022664000"
# "V's hash 70122022646420"
# "V's eql?"
# "V's hash 70122022646420"
# "V's hash 70122022664000"
 => {#<V:0x007f8d1c80ee80>=>6} 
# {#<V:0x007f8d1c8bb0e0>=>7} 

{ k => 5, v => 7, V.new => 8}
# "V's hash 70122022664000"
# "V's hash 70122022646420"
# "V's eql?"
# "V's hash 70122022646420"
# "V's hash 70122022664000"
# "V's hash 70122014579860"
# "V's eql?"
# "V's hash 70122014579860"
# "V's hash 70122022664000"
# => {#<V:0x007f8d1c80ee80>=>8} 
  • If you redefine hash method with different hash value in later(duck-typing), then the hash value of entries in set will not change, which result inconsistent hash values.
  • Caveat: Always make hash method returns consistent values in all time(don't do duck typing on #hash method), or you have to update the hash values for each entries.
class Point
  def eql?(b)
    p "eql? #{b}"
    #super
  end
  def hash
    p "hash #{self}"
    0
  end
end

v = Point.new 
p = Point.new 
require 'set'
s = Set.new
s << v
s.include? p
# "hash #<Point:0x007fd21a868c58>"
# "eql? #<Point:0x007fd21a879300>"

class Point
  def hash
   p "hash changed #{self}"
   21345
  end
end

# Now the hash value for v has changed. 
# But in set s, its entry still keep previous hash value for instance v.

s.include? v
# "hash changed #<Point:0x007fd21a879300>"
# false

Direct Instance from Class

  • If we create an instance through the Class initialization, then it is instance's class will be Class rather than its implemented class name, this implemented class will be super class of that Class. So the strcitly class name comparison may not work, e.g. p2.class == p1.class.
class Point
  def initialize(x, y)
    @x, @y = x, y
  end
end

p = Class.new(Point) do
  def to_s
    "#{@x}, #{@y}"
  end
end

p1 = Point.new(2,1)
p.new(2,1)
# <#<Class:0x007f85ab8f7c70>:0x007f85aa07f2d8 @x=2, @y=1> 
p2.class 
# Class

# p2 cannot equal to p1, though implementation are the same.
p2.class == p1.class
# false

p2.superclass
# Point
p2.is_a? p1.class
# false, p2 is not an instance of Point.

block, lambda, and Proc

  1. block === proc are almost the same. Both accept var args, lambda restrict args count must match as its declaration.
  2. lambda is like a method definition, proc are more like a code snippet reside in some other method definition or code block.
  3. proc act as similar as code snippet inside method scope. So when you call proc in that scope and proc has return keyword, then it will return from that outers cope and stop rest of code in that outer scope.
  4. lambda just act as normal method, return from its lambda scope and continue execution for outer scope.

References

http://www.reactive.io/tips/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/ http://ixti.net/development/ruby/2011/09/03/understanding-rack-builder.html
http://juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby/
http://ruby-metaprogramming.rubylearning.com/html/ruby_metaprogramming_3.html
http://yehudakatz.com/2009/11/15/metaprogramming-in-ruby-its-all-about-the-self
http://www.infoq.com/presentations/metaprogramming-ruby
http://www.hokstad.com/ruby-object-model
http://ruby-metaprogramming.rubylearning.com/html/seeingMetaclassesClearly.html
Book of Ruby
http://pragprog.com/book/ruby/programming-ruby
http://ruby-metaprogramming.rubylearning.com/
http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Method_Calls#Blocks
http://rubylearning.com/blog/2010/11/30/how-do-i-build-dsls-with-yield-and-instance_eval/

@seanfcarroll

This comment has been minimized.

Copy link

seanfcarroll commented Feb 19, 2016

WOW!

@vasilakisfil

This comment has been minimized.

Copy link

vasilakisfil commented Mar 27, 2017

Great stuff!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.