Ste*_*fan 10 ruby closures proc
由于 proc 是一个对象,我可以在它自己的实例范围内创建一个 proc 吗?
例如:
prc = Proc.new do
foo
end
def prc.foo
123
end
prc.call
# NameError: undefined local variable or method `foo' for main:Object
Run Code Online (Sandbox Code Playgroud)
通过更改self或明确receiver引用 proc。
该接收器必须动态评估,例如以下应该工作:
other_prc = prc.clone
def other_prc.foo
456
end
other_prc.call
#=> 456 <- not 123
Run Code Online (Sandbox Code Playgroud)
这意味着我不能通过以下方式对其进行“硬编码”:
prc = Proc.new do
prc.foo
end
Run Code Online (Sandbox Code Playgroud)
换句话说:有没有办法从 proc 内部引用 procs 实例?
另一个没有的例子foo:(放什么# ???)
prc = Proc.new do
# ???
end
prc == prc.call #=> true
other_prc = prc.clone
other_prc == other_prc.call #=> true
Run Code Online (Sandbox Code Playgroud)
替换# ???withprc只会满足prc == prc.call而不是other_prc == other_prc.call。(因为other_prc.call还是会回来prc)
免责声明:我正在回答我自己的问题
解决方案出奇的简单。只需覆盖call即可通过instance_exec以下方式调用 proc :
在接收者(obj)的上下文中执行给定的块。为了设置上下文,在代码执行时将变量
self设置为obj,使代码可以访问obj的实例变量。参数作为块参数传递。
prc = proc { |arg|
@a ||= 0
@a += 1
p self: self, arg: arg, '@a': @a
}
def prc.call(*args)
instance_exec(*args, &self)
end
Run Code Online (Sandbox Code Playgroud)
在这里,接收者是 proc 本身,“给定块”也是 proc 本身。instance_exec因此将在它自己的实例的上下文中调用 proc。它甚至会传递任何额外的参数!
使用上述:
prc
#=> #<Proc:0x00007f84d29dcbb0>
prc.call(:foo)
#=> {:self=>#<Proc:0x00007f84d29dcbb0>, :arg=>:foo, :@a=>1}
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^
# correct object passes args
prc.call(:bar)
#=> {:self=>#<Proc:0x00007f84d29dcbb0>, :arg=>:bar, :@a=>2}
# ^^^^^^
# preserves ivars
prc.instance_variable_get(:@a)
#=> 2 <- actually stores ivars in the proc instance
other_prc = prc.clone
#=> #<Proc:0x00007f84d29dc598>
# ^^^^^^^^^^^^^^^^^^
# different object
other_prc.call(:baz)
#=> {:self=>#<Proc:0x00007f84d29dc598>, :arg=>:baz, :@a=>3}
# ^^^^^^
# ivars are cloned
other_prc.call(:qux)
#=> {:self=>#<Proc:0x00007f84d29dc598>, :arg=>:qux, :@a=>4}
prc.call(:quux)
#=> {:self=>#<Proc:0x00007f84d29dcbb0>, :arg=>:quux, :@a=>3}
# ^^^^^^
# both instances have separate ivars
Run Code Online (Sandbox Code Playgroud)
通常在 DSL 中使用的一般方法称为 Clean Room 模式 - 您为评估 DSL 代码块而构建的对象。它用于限制 DSL 访问不需要的方法,以及定义 DSL 处理的底层数据。
该方法看起来像这样:
# 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
Run Code Online (Sandbox Code Playgroud)
看来您正在寻找的是让 Proc 对象本身既用作块又用作无尘室。这有点不寻常,因为您通常希望在不同的基础数据上重用代码块。我建议您首先考虑原始模式是否更适合您的应用程序。
尽管如此,确实可以将 Proc 对象用作洁净室,并且代码看起来与上面的模式非常相似(代码看起来也类似于您在答案中发布的方法):
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
Run Code Online (Sandbox Code Playgroud)
您还可以考虑通过创建一个从 Proc 继承而不是覆盖本机call方法的新类来使该方法更易于运行。覆盖它本身并没有错,但是您可能需要灵活地将它附加到不同的接收器,因此这种方法可以让您同时拥有:
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
Run Code Online (Sandbox Code Playgroud)
如果由于某种原因您不能使用新类,例如您从 gem 获取 Proc 对象,您可以考虑使用模块扩展 Proc 对象:
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
# ...
Run Code Online (Sandbox Code Playgroud)