Ruby Assign Context to Lambda

Ruby assign context to lambda?

Yeah, but be careful with it, this one is really easy to abuse. I would personally be apprehensive of code like this.

class Rule
def get_rule
Proc.new { puts name }
end
end

class Person
attr_accessor :name

def init_rule
@name = "ruby"
instance_eval(&Rule.new.get_rule)
end
end

ruby lambda context

the code you are looking for is

temp.instance_eval(&callable)

when calling instance_eval(&lambda) to pass current context got error 'wrong number of arguments'

You are actually correct in your assumption. Self is being passed to the Proc and to the lambda as it is being instance_eval'ed. A major difference between Procs and lambdas is that lambdas check the arity of the block being being passed to them.

So:

 class Rule
def get_rule
lambda { |s| puts s.inspect; puts name; }
end
end

class Person
attr_accessor :name

def init_rule
@name = "ruby"
instance_eval(&Rule.new.get_rule)
end
end

p = Person.new
p.init_rule

#<Person:0x007fd1099f53d0 @name="ruby">
ruby

Here I told the lambda to expect a block with arity 1 and as you see in the argument inspection, the argument is indeed the self instance of Person class.

Change the binding of a Proc in Ruby

You can try the following hack:

class Proc
def call_with_vars(vars, *args)
Struct.new(*vars.keys).new(*vars.values).instance_exec(*args, &self)
end
end

To be used like this:

irb(main):001:0* lambda { foo }.call_with_vars(:foo => 3)
=> 3
irb(main):002:0> lambda { |a| foo + a }.call_with_vars({:foo => 3}, 1)
=> 4

This is not a very general solution, though. It would be better if we could give it Binding instance instead of a Hash and do the following:

l = lambda { |a| foo + a }
foo = 3
l.call_with_binding(binding, 1) # => 4

Using the following, more complex hack, this exact behaviour can be achieved:

class LookupStack
def initialize(bindings = [])
@bindings = bindings
end

def method_missing(m, *args)
@bindings.reverse_each do |bind|
begin
method = eval("method(%s)" % m.inspect, bind)
rescue NameError
else
return method.call(*args)
end
begin
value = eval(m.to_s, bind)
return value
rescue NameError
end
end
raise NoMethodError
end

def push_binding(bind)
@bindings.push bind
end

def push_instance(obj)
@bindings.push obj.instance_eval { binding }
end

def push_hash(vars)
push_instance Struct.new(*vars.keys).new(*vars.values)
end

def run_proc(p, *args)
instance_exec(*args, &p)
end
end

class Proc
def call_with_binding(bind, *args)
LookupStack.new([bind]).run_proc(self, *args)
end
end

Basically we define ourselves a manual name lookup stack and instance_exec our proc against it. This is a very flexible mechanism. It not only enables the implementation of call_with_binding, it can also be used to build up much more complex lookup chains:

l = lambda { |a| local + func(2) + some_method(1) + var + a }

local = 1
def func(x) x end

class Foo < Struct.new(:add)
def some_method(x) x + add end
end

stack = LookupStack.new
stack.push_binding(binding)
stack.push_instance(Foo.new(2))
stack.push_hash(:var => 4)

p stack.run_proc(l, 5)

This prints 15, as expected :)

UPDATE: Code is now also available at Github. I use this for one my projects too now.

Can I create a proc in the context of itself?

A general approach that is typically used in DSLs is referred to as the Clean Room pattern - an object you build for the purpose of evaluating blocks of DSL code. It is used to restrict the DSL from accessing undesired methods, as well as to define the underlying data the DSL works on.

The approach looks something like this:

# Using struct for simplicity.
# The clean room can be a full-blown class.
first_clean_room = Struct.new(:foo).new(123)
second_clean_room = Struct.new(:foo).new(321)

prc = Proc.new do
foo
end

first_clean_room.instance_exec(&prc)
# => 123

second_clean_room.instance_exec(&prc)
# => 321

It appears that what you are looking for is to have the Proc object itself serve as both the block and the clean room. This is a bit unusual, since the block of code is what you typically want to have reused on different underlying data. I suggest you consider first whether the original pattern might be a better fit for your application.

Nevertheless, having the Proc object serve as the clean room can indeed be done, and the code looks very similar to the pattern above (the code also looks similar to the approach you posted in your answer):

prc = Proc.new do 
foo
end

other = prc.clone

# Define the attributes in each clean room

def prc.foo
123
end

def other.foo
321
end

prc.instance_exec(&prc)
# => 123

other.instance_exec(&other)
# => 321

You could also consider making the approach more convenient to run by creating a new class that inherits from Proc instead of overriding the native call method. It's not wrong per-se to override it, but you might need the flexibility to attach it to a different receiver, so this approach lets you have both:

class CleanRoomProc < Proc
def run(*args)
instance_exec(*args, &self)
end
end

code = CleanRoomProc.new do
foo
end

prc = code.clone
other = code.clone

def prc.foo
123
end

def other.foo
321
end

prc.run
# => 123

other.run
# => 321

And if you cannot use a new class for some reason, e.g. you are getting a Proc object from a gem, you could consider extending the Proc object using a module:

module SelfCleanRoom
def run(*args)
instance_exec(*args, &self)
end
end

code = Proc.new do
foo
end

code.extend(SelfCleanRoom)

prc = code.clone
other = code.clone

# ...

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

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

Return statements inside procs, lambdas, and blocks

As one answer in the linked question shows:

The return keyword always returns from the method or lambda in the current context. In blocks, it will return from the method in which the closure was defined. It cannot be made to return from the calling method or lambda.

Your first example was successful because you defined victor in the same function you wanted to return from, so a return was legal in that context. In your second example, victor was defined in the top-level. The effect of that return, then, would not be to return from batman_yield (the calling method), but [if it were valid] to return from the top-level itself (where the Proc was defined).

Clarification: while you can access the return value of a block (i.e. "The value of the last expression evaluated in the block is passed back to the method as the value of the yield" - as per your comment), you can't use the return keyword, for the reason stated above. Example:

def batman_yield
value = yield
return value
"Iron man will win!"
end

victor = Proc.new { return "Batman will win!" }
victor2 = Proc.new { "Batman will win!" }

#batman_yield(&victor) === This code throws an error.
puts batman_yield(&victor2) # This code works fine.

In Ruby is there a way to define the context for load?

In this particular case, getting what you want is quite easy, and doesn't even involve a call to load:

foo.instance_eval(File.read(file))

For example, if file just had the text length, then

"Hello".instance_eval(File.read(file))

would return 5.

More generally, if you want to specify an arbitrary context, that's basically the exact situation you'd want to use the optional Binding argument to Kernel#eval in:

bnd = 'Hello'.instance_exec { x = 3; binding }
eval('length + x',bnd) #=> 8

Assigning procs to constants

That is a bad practice. Defining constants on the main level is as bad as using global variables. That defeats the purpose of object oriented programming (OOP). In OOP, you want to hide unnecessary things as much as possible. If something always works on a string, then that should not be accessible outside of the context of a string, and that should be defined on the String class. If something always provides some form of a time, then that should be defined on the Time class, either as a class or an instance method depending on the nature of the method.

How do I using instance variables from within a lambda/Proc defined in a class variable?

You could use instance_exec to supply the appropriate context for the lambdas when you call them, look for the comments to see the changes:

class Actions
# Move the lambdas to a class variable, a COMMANDS constant
# would work just as well and might be more appropriate.
@@commands = {
"ADD" => ->(name) { @people << name },
"REMOVE" => ->(n = 0) { puts "Goodbye" },
"OTHER" => ->(n = 0) { puts "Do Nothing" }
}
def initialize
@people = [ ]
end
def run_command(cmd, *param)
# Use instance_exec and blockify the lambdas with '&'
# to call them in the context of 'self'. Change the
# @@commands to COMMANDS if you prefer to use a constant
# for this stuff.
instance_exec(param, &@@commands[cmd]) if @@commands.key?(cmd)
end
def people
@people
end
end


Related Topics



Leave a reply



Submit