Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

ruby

self

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..

like image 831
yaquawa Avatar asked Nov 01 '25 15:11

yaquawa


1 Answers

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:

  • BasicObject#instance_exec
  • Module#module_exec
  • BasicObject#instance_eval
  • Module#module_eval
  • Class#module_eval

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の話.

like image 66
Jörg W Mittag Avatar answered Nov 04 '25 15:11

Jörg W Mittag



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!