class Object
  def let *a, &b
    Let.evaluate *a, &b
  end

  def as local, kvs = {}, &block
    kvs.update local => self
    Let.evaluate kvs, &block
  end

  class Let
    instance_methods.each{|m| undef_method m unless m[%r/^__/]}

    Instance_eval = Object.instance_method :instance_eval
    Instance_variable_set = Object.instance_method :instance_variable_set

    def self.evaluate kvs = {}, &block
      current = {}
      binding = block.binding
      undefined = Object.new
      begin
        push_vars kvs, current, binding, undefined
        let = new
        singleton_class =
          class << let
            self
          end
        instance_eval = Instance_eval.bind let
        instance_variable_set = Instance_variable_set.bind let
        singleton_class.module_eval{ kvs.keys.each{|k| attr k} }
        instance_eval.call{ kvs.each{|k,v| instance_variable_set.call "@#{ k }", v} }
        instance_eval.call &block
      ensure
        pop_vars current, binding, undefined
      end
    end

    def self.push_vars kvs, current, binding, undefined
      kvs.each do |k,v|
        current[k] =
          begin
            eval "#{ k }", binding
          rescue NameError
            undefined
          end
        eval "#{ k } = ObjectSpace._id2ref #{ v.object_id }", binding unless
          current[k] == undefined
      end
    end

    def self.pop_vars current, binding, undefined
      current.each do |k,v|
        next if v == undefined
        eval "#{ k } = ObjectSpace._id2ref #{ v.object_id }", binding
      end
    end
  end
end


a = 40
b = 2
c = 'forty-two'

p let(:x => a, :y => b){ x + y } #=> 42
p a.as(:b){ a + b }              #=> 80
p a.as(:x, :y => b){ x + y }     #=> 42
p let(:c => a, :d => b){ c + d } #=> 42
p a                              #=> 40
p b                              #=> 2
p c                              #=> "forty-two"