Search code examples
rubyself

How does ruby resolve the `self` keyword in lambda or block?


Just like JavaScript, in ruby, lambda can be passed over functions.

In JavaScript this will be resolved as the caller object.

But what about ruby? Does the same mechanism applied to ruby's lambda or block too? How? Can you give me some example code?

By the way I've read the The Ruby Programming Language, but I couldn't find any helpful info from it..


Solution

  • In Ruby, self is lexically scoped, i.e. self inside a block or lambda is whatever it would be at that same place without being in a block or lambda.

    class << foo = Object.new
      def bar
        puts "`self` inside `foo#bar` is #{self.inspect}"
        yield self
      end
    end
    
    this = self
    
    foo.bar do |that|
      puts "`self` inside the block is #{self.inspect}"
      case
      when this.equal?(self)
        puts "… which is the same as outside the block."
      when that.equal?(self)
        puts "… which is the same as inside the method."
      else
        puts "Ruby is really weird, `self` is something completely different!"
      end
    end
    
    # `self` inside `foo#bar` is #<Object:0xdeadbeef48151623>
    # `self` inside the block is main
    # … which is the same as outside the block.
    

    This also applies to lambdas:

    class << foo = Object.new
      def bar(lambda)
      #      ↑↑↑↑↑↑↑↑
        puts "`self` inside `foo#bar` is #{self.inspect}"
        lambda.(self)
        #↑↑↑↑↑↑↑↑↑↑↑↑
      end
    end
    
    this = self
    
    foo.bar(-> that do
      #    ↑↑↑↑↑↑↑↑
      puts "`self` inside the lambda is #{self.inspect}"
      case
      when this.equal?(self)
        puts "… which is the same as outside the lambda."
      when that.equal?(self)
        puts "… which is the same as inside the method."
      else
        puts "Ruby is really weird, `self` is something completely different!"
      end
    end)
    
    # `self` inside `foo#bar` is #<Object:0xdeadbeef48151623>
    # `self` inside the lambda is main
    # … which is the same as outside the lambda.
    

    There are, however, a fixed number of very specific reflective methods that do change self, those are the methods in the *_{exec|eval} family:

    Example (changing only one relevant line from above):

    class << foo = Object.new
      def bar(&blk)
      #      ↑↑↑↑↑↑
        puts "`self` inside `foo#bar` is #{self.inspect}"
        instance_exec(self, &blk)
        #↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
      end
    end
    
    this = self
    
    foo.bar do |that|
      puts "`self` inside the block is #{self.inspect}"
      case
      when this.equal?(self)
        puts "… which is the same as outside the block."
      when that.equal?(self)
        puts "… which is the same as inside the method."
      else
        puts "Ruby is really weird, `self` is something completely different!"
      end
    end
    
    # `self` inside `foo#bar` is #<Object:0xdeadbeef48151623>
    # `self` inside the block is #<Object:0xdeadbeef48151623>
    # … which is the same as inside the method.
    #                        ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
    

    This also applies to lambdas (converted to blocks):

    foo.bar(&-> that do
      #    ↑↑↑↑↑↑↑↑
      puts "`self` inside the lambda is #{self.inspect}"
      case
      when this.equal?(self)
        puts "… which is the same as outside the lambda."
      when that.equal?(self)
        puts "… which is the same as inside the method."
      else
        puts "Ruby is really weird, `self` is something completely different!"
      end
    end)
    
    # `self` inside `foo#bar` is #<Object:0xdeadbeef48151623>
    # `self` inside the lambda is #<Object:0xdeadbeef48151623>
    # … which is the same as inside the method.
    #                        ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
    

    Lastly, Ruby allows you to reflectively reify the lexical environment at a specific call-site into a Binding object. You can then, in turn, evaluate code in the context of this specific binding using either the binding's Binding#eval method or by passing the binding object to Kernel#eval:

    class << foo = Object.new
      def bar(str)
        puts "`self` inside `foo#bar`         is #{self.inspect}"
        binding.eval(str)
        #↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
      end
    end
    
    $this = self
    
    foo.bar <<~'HERE'
      puts "`self` inside the eval'd string is #{self.inspect}"
      if $this.equal?(self)
        puts "… which is the same as outside the eval'd string."
      end
    HERE
    
    # `self` inside `foo#bar`         is #<Object:0x0070070070070070>
    # `self` inside the eval'd string is #<Object:0x0070070070070070>
    

    self is one of three implicit contexts in Ruby:

    • self
    • the default definee
    • constant lookup context

    There is a nice blog article by yugui, which mostly talks about the default definee but also touches briefly on self: Three implicit contexts in Ruby. There is also an older article in Japanese which goes into a little more detail: Rubyの呼び出し可能オブジェクトの比較 (3) - なんかklassの話.