我可以在它自己的上下文中创建一个 proc 吗?
Can I create a proc in the context of itself?
既然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
通过更改 self
或明确 receiver
引用过程。
必须动态评估该接收器,例如以下应该有效:
other_prc = prc.clone
def other_prc.foo
456
end
other_prc.call
#=> 456 <- not 123
这意味着我不能通过以下方式“硬编码”它:
prc = Proc.new do
prc.foo
end
换句话说:有没有办法从 proc 中引用 procs 实例?
另一个没有 foo
的例子:(为 # ???
放什么)
prc = Proc.new do
# ???
end
prc == prc.call #=> true
other_prc = prc.clone
other_prc == other_prc.call #=> true
用prc
代替# ???
只能满足prc == prc.call
,不能满足other_prc == other_prc.call
。 (因为 other_prc.call
仍然会 return prc
)
评论后编辑的第二次尝试
# This solution has a limit you have to return the `Proc` itself
with_proc = proc do |aproc, others|
aproc.instance_variable_set(:@a, aproc.instance_variable_get(:@a) || 0)
aproc.instance_variable_set(:@a, aproc.instance_variable_get(:@a) + 1)
p self: aproc, arg: others, '@a': aproc.instance_variable_get(:@a)
aproc
end
prc = with_proc.(with_proc, :foo)
# => {:self=>#<Proc:0x000055864be1a740@pro_self.rb:1>, :arg=>:foo, :@a=>1}
puts "prc: #{prc}"
puts "prc.equal?(with_proc): #{prc.equal?(with_proc)}"
# => prc: #<Proc:0x000055864be1a740@pro_self.rb:1>
# => prc.equal?(with_proc): true
prc.call(prc, :bar)
puts "prc @a: #{prc.instance_variable_get(:@a)}"
# => {:self=>#<Proc:0x000055864be1a740@pro_self.rb:1>, :arg=>:bar, :@a=>2}
# => prc @a: 2
other_prc = prc.call(prc.clone, :baz)
puts "other_prc: #{other_prc}"
# => {:self=>#<Proc:0x000055864be1a0b0@pro_self.rb:1>, :arg=>:baz, :@a=>3}
# => other_prc: #<Proc:0x000055864be1a0b0@pro_self.rb:1>
other_prc.call(other_prc, :qux)
#=> {:self=>#<Proc:0x000055864be1a0b0@pro_self.rb:1>, :arg=>:qux, :@a=>4}
prc.call(prc, :quux)
# => {:self=>#<Proc:0x000055864be1a740@pro_self.rb:1>, :arg=>:quux, :@a=>3}
使用此解决方案,您可以return任何需要的东西
prc = proc do |ref_to_self, others|
self_reference = ref_to_self.instance_variable_get(:@ident)
self_reference.instance_variable_set(:@a, self_reference.instance_variable_get(:@a) || 0)
self_reference.instance_variable_set(:@a, self_reference.instance_variable_get(:@a) + 1)
p ({self: self_reference.instance_variable_get(:@ident),
arg: others,
'@a': self_reference.instance_variable_get(:@a)})
end
prc.instance_variable_set(:@ident, prc)
prc.call(prc, :foo)
puts "prc: #{prc}"
prc.call(prc, :bar)
puts "prc @a: #{prc.instance_variable_get(:@a)}"
other_prc = prc.clone
other_prc.instance_variable_set(:@ident, other_prc)
other_prc.call(other_prc, :baz)
puts "other_prc: #{other_prc}"
other_prc.call(other_prc, :qux)
prc.call(prc, :quux)
# {:self=>#<Proc:0x00005559f1f6d808@pro_self.rb:71>, :arg=>:foo, :@a=>1}
# prc: #<Proc:0x00005559f1f6d808@pro_self.rb:71>
# {:self=>#<Proc:0x00005559f1f6d808@pro_self.rb:71>, :arg=>:bar, :@a=>2}
# prc @a: 2
# {:self=>#<Proc:0x00005559f1f6d1f0@pro_self.rb:71>, :arg=>:baz, :@a=>3}
# other_prc: #<Proc:0x00005559f1f6d1f0@pro_self.rb:71>
# {:self=>#<Proc:0x00005559f1f6d1f0@pro_self.rb:71>, :arg=>:qux, :@a=>4}
# {:self=>#<Proc:0x00005559f1f6d808@pro_self.rb:71>, :arg=>:quux, :@a=>3}
第一次尝试
评论后编辑。我知道没有直接的方法来引用您传递给 new
的块内的 Proc
对象 我尝试使用 tap 来更接近您的代码。
我希望这可以帮助
def proc_reference_to_self(a_proc)
first = Proc.new do
puts "Hello"
end.tap(&a_proc)
end
second_prc = Proc.new do |first|
p first
first.call
puts "second_prc"
p second_prc
end
# This execute second_prc as a block
proc_reference_to_self(second_prc)
# first and second are different objects but you can still reference first
# inside second_proc
# <Proc:0x000055603a8c72e8@ruby_array_of_paths.rb:75>
# Hello
# second_prc
# <Proc:0x000055603a8c7338@ruby_array_of_paths.rb:81>
利用闭包的外部范围
如果我正确理解你的问题,利用闭包的外部范围可能会达到你想要的效果。不可否认,这是一个非常人为的示例,它在数组中注册嵌套的 Proc 对象。在调用第一个 Proc 之前不会创建第二个 Proc,但它们都保留了与外部作用域的绑定。
@procs = []
@foo = 1
@procs << proc do
# Don't keep re-registering the nested Proc on
# subsequent invocations.
@procs << proc { @foo + 1 } unless @procs.count == 2
@foo
end
@procs.map &:call
#=> [1, 2]
@foo = 3
@procs.map &:call
#=> [3, 4]
好的,现在我想我明白你的意思了。正如我在评论中提到的,它可以通过嵌套闭包来完成。因为 Procs/lambdas 是匿名的,闭包嵌套为 lambda 提供了一种接收对其自身的动态引用的方法,从而允许它在 self
.[=16= 的上下文中 instance_eval
代码]
wrapped_dispatch = ->(f) { f[f] }
proc_wrapped = lambda do |myself|
lambda do |n|
myself.instance_eval do
# in context of self
bar(n)
end
end
end
def proc_wrapped.bar(n)
p "bar #{n}"
end
wrapped_dispatch[proc_wrapped].call(123)
# => "bar 123"
# can also save it "unwrapped"
prc = wrapped_dispatch[proc_wrapped]
prc.call(123)
# => "bar 123"
# Very late binding to dynamic receiver
def proc_wrapped.bar(n)
p "BAR #{n}"
end
prc.call(123)
# => "BAR 123"
# and if the "wrapped-ness" bothers you, link them together and delegate
proc_wrapped.define_singleton_method(:call) do |n|
prc.call(n)
end
def proc_wrapped.bar(n)
p "BBBBAAAARRRRR"
end
proc_wrapped.call(123)
# => "BBBBAAAARRRRR"
other_proc_wrapped = proc_wrapped.clone
wrapped_dispatch[other_proc_wrapped].call(123)
# => "BBBBAAAARRRRR"
def other_proc_wrapped.bar(n)
p "foo #{n}"
end
wrapped_dispatch[other_proc_wrapped].call(123)
# => "foo 123"
proc_wrapped.call(123)
# => "BBBBAAAARRRRR"
我注意到这种行为非常类似于 class 的实例 (Foo.new
) 与 class 的单例 class (Foo.singleton_class
),这是有道理的,因为 closures and objects are equivalent。这意味着如果你真的想要这样的行为,你应该只使用 class、它的单例 class 和它的实例,就像 Ruby.
中惯用的那样
免责声明:我正在回答我自己的问题
解决方案非常简单。只需覆盖 call
即可通过 instance_exec
:
调用过程
Executes the given block within the context of the receiver (obj). In order to set the context, the variable self
is set to obj while the code is executing, giving the code access to obj's instance variables. Arguments are passed as block parameters.
prc = proc { |arg|
@a ||= 0
@a += 1
p self: self, arg: arg, '@a': @a
}
def prc.call(*args)
instance_exec(*args, &self)
end
这里,接收者是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
通常在 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
看来您正在寻找的是让 Proc 对象本身既充当块又充当洁净室。这有点不寻常,因为代码块通常是您希望在不同的基础数据上重用的内容。我建议您首先考虑原始模式是否更适合您的应用程序。
不过,将Proc对象作为clean room确实是可以做到的,而且代码看起来和上面的模式非常相似(代码看起来也和你在答案中贴出的方法相似):
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
您还可以考虑通过创建一个继承自 Proc 的新 class 而不是覆盖本机 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
如果您由于某种原因无法使用新的 class,例如您正在从 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
# ...
既然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
通过更改 self
或明确 receiver
引用过程。
必须动态评估该接收器,例如以下应该有效:
other_prc = prc.clone
def other_prc.foo
456
end
other_prc.call
#=> 456 <- not 123
这意味着我不能通过以下方式“硬编码”它:
prc = Proc.new do
prc.foo
end
换句话说:有没有办法从 proc 中引用 procs 实例?
另一个没有 foo
的例子:(为 # ???
放什么)
prc = Proc.new do
# ???
end
prc == prc.call #=> true
other_prc = prc.clone
other_prc == other_prc.call #=> true
用prc
代替# ???
只能满足prc == prc.call
,不能满足other_prc == other_prc.call
。 (因为 other_prc.call
仍然会 return prc
)
评论后编辑的第二次尝试
# This solution has a limit you have to return the `Proc` itself
with_proc = proc do |aproc, others|
aproc.instance_variable_set(:@a, aproc.instance_variable_get(:@a) || 0)
aproc.instance_variable_set(:@a, aproc.instance_variable_get(:@a) + 1)
p self: aproc, arg: others, '@a': aproc.instance_variable_get(:@a)
aproc
end
prc = with_proc.(with_proc, :foo)
# => {:self=>#<Proc:0x000055864be1a740@pro_self.rb:1>, :arg=>:foo, :@a=>1}
puts "prc: #{prc}"
puts "prc.equal?(with_proc): #{prc.equal?(with_proc)}"
# => prc: #<Proc:0x000055864be1a740@pro_self.rb:1>
# => prc.equal?(with_proc): true
prc.call(prc, :bar)
puts "prc @a: #{prc.instance_variable_get(:@a)}"
# => {:self=>#<Proc:0x000055864be1a740@pro_self.rb:1>, :arg=>:bar, :@a=>2}
# => prc @a: 2
other_prc = prc.call(prc.clone, :baz)
puts "other_prc: #{other_prc}"
# => {:self=>#<Proc:0x000055864be1a0b0@pro_self.rb:1>, :arg=>:baz, :@a=>3}
# => other_prc: #<Proc:0x000055864be1a0b0@pro_self.rb:1>
other_prc.call(other_prc, :qux)
#=> {:self=>#<Proc:0x000055864be1a0b0@pro_self.rb:1>, :arg=>:qux, :@a=>4}
prc.call(prc, :quux)
# => {:self=>#<Proc:0x000055864be1a740@pro_self.rb:1>, :arg=>:quux, :@a=>3}
使用此解决方案,您可以return任何需要的东西
prc = proc do |ref_to_self, others|
self_reference = ref_to_self.instance_variable_get(:@ident)
self_reference.instance_variable_set(:@a, self_reference.instance_variable_get(:@a) || 0)
self_reference.instance_variable_set(:@a, self_reference.instance_variable_get(:@a) + 1)
p ({self: self_reference.instance_variable_get(:@ident),
arg: others,
'@a': self_reference.instance_variable_get(:@a)})
end
prc.instance_variable_set(:@ident, prc)
prc.call(prc, :foo)
puts "prc: #{prc}"
prc.call(prc, :bar)
puts "prc @a: #{prc.instance_variable_get(:@a)}"
other_prc = prc.clone
other_prc.instance_variable_set(:@ident, other_prc)
other_prc.call(other_prc, :baz)
puts "other_prc: #{other_prc}"
other_prc.call(other_prc, :qux)
prc.call(prc, :quux)
# {:self=>#<Proc:0x00005559f1f6d808@pro_self.rb:71>, :arg=>:foo, :@a=>1}
# prc: #<Proc:0x00005559f1f6d808@pro_self.rb:71>
# {:self=>#<Proc:0x00005559f1f6d808@pro_self.rb:71>, :arg=>:bar, :@a=>2}
# prc @a: 2
# {:self=>#<Proc:0x00005559f1f6d1f0@pro_self.rb:71>, :arg=>:baz, :@a=>3}
# other_prc: #<Proc:0x00005559f1f6d1f0@pro_self.rb:71>
# {:self=>#<Proc:0x00005559f1f6d1f0@pro_self.rb:71>, :arg=>:qux, :@a=>4}
# {:self=>#<Proc:0x00005559f1f6d808@pro_self.rb:71>, :arg=>:quux, :@a=>3}
第一次尝试
评论后编辑。我知道没有直接的方法来引用您传递给 new
的块内的 Proc
对象 我尝试使用 tap 来更接近您的代码。
我希望这可以帮助
def proc_reference_to_self(a_proc)
first = Proc.new do
puts "Hello"
end.tap(&a_proc)
end
second_prc = Proc.new do |first|
p first
first.call
puts "second_prc"
p second_prc
end
# This execute second_prc as a block
proc_reference_to_self(second_prc)
# first and second are different objects but you can still reference first
# inside second_proc
# <Proc:0x000055603a8c72e8@ruby_array_of_paths.rb:75>
# Hello
# second_prc
# <Proc:0x000055603a8c7338@ruby_array_of_paths.rb:81>
利用闭包的外部范围
如果我正确理解你的问题,利用闭包的外部范围可能会达到你想要的效果。不可否认,这是一个非常人为的示例,它在数组中注册嵌套的 Proc 对象。在调用第一个 Proc 之前不会创建第二个 Proc,但它们都保留了与外部作用域的绑定。
@procs = []
@foo = 1
@procs << proc do
# Don't keep re-registering the nested Proc on
# subsequent invocations.
@procs << proc { @foo + 1 } unless @procs.count == 2
@foo
end
@procs.map &:call
#=> [1, 2]
@foo = 3
@procs.map &:call
#=> [3, 4]
好的,现在我想我明白你的意思了。正如我在评论中提到的,它可以通过嵌套闭包来完成。因为 Procs/lambdas 是匿名的,闭包嵌套为 lambda 提供了一种接收对其自身的动态引用的方法,从而允许它在 self
.[=16= 的上下文中 instance_eval
代码]
wrapped_dispatch = ->(f) { f[f] }
proc_wrapped = lambda do |myself|
lambda do |n|
myself.instance_eval do
# in context of self
bar(n)
end
end
end
def proc_wrapped.bar(n)
p "bar #{n}"
end
wrapped_dispatch[proc_wrapped].call(123)
# => "bar 123"
# can also save it "unwrapped"
prc = wrapped_dispatch[proc_wrapped]
prc.call(123)
# => "bar 123"
# Very late binding to dynamic receiver
def proc_wrapped.bar(n)
p "BAR #{n}"
end
prc.call(123)
# => "BAR 123"
# and if the "wrapped-ness" bothers you, link them together and delegate
proc_wrapped.define_singleton_method(:call) do |n|
prc.call(n)
end
def proc_wrapped.bar(n)
p "BBBBAAAARRRRR"
end
proc_wrapped.call(123)
# => "BBBBAAAARRRRR"
other_proc_wrapped = proc_wrapped.clone
wrapped_dispatch[other_proc_wrapped].call(123)
# => "BBBBAAAARRRRR"
def other_proc_wrapped.bar(n)
p "foo #{n}"
end
wrapped_dispatch[other_proc_wrapped].call(123)
# => "foo 123"
proc_wrapped.call(123)
# => "BBBBAAAARRRRR"
我注意到这种行为非常类似于 class 的实例 (Foo.new
) 与 class 的单例 class (Foo.singleton_class
),这是有道理的,因为 closures and objects are equivalent。这意味着如果你真的想要这样的行为,你应该只使用 class、它的单例 class 和它的实例,就像 Ruby.
免责声明:我正在回答我自己的问题
解决方案非常简单。只需覆盖 call
即可通过 instance_exec
:
Executes the given block within the context of the receiver (obj). In order to set the context, the variable
self
is set to obj while the code is executing, giving the code access to obj's instance variables. Arguments are passed as block parameters.
prc = proc { |arg|
@a ||= 0
@a += 1
p self: self, arg: arg, '@a': @a
}
def prc.call(*args)
instance_exec(*args, &self)
end
这里,接收者是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
通常在 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
看来您正在寻找的是让 Proc 对象本身既充当块又充当洁净室。这有点不寻常,因为代码块通常是您希望在不同的基础数据上重用的内容。我建议您首先考虑原始模式是否更适合您的应用程序。
不过,将Proc对象作为clean room确实是可以做到的,而且代码看起来和上面的模式非常相似(代码看起来也和你在答案中贴出的方法相似):
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
您还可以考虑通过创建一个继承自 Proc 的新 class 而不是覆盖本机 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
如果您由于某种原因无法使用新的 class,例如您正在从 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
# ...