Feature #16461
openProc#using
Description
Overview¶
I propose Proc#using to support block-level refinements.
module IntegerDivExt
refine Integer do
def /(other)
quo(other)
end
end
end
def instance_eval_with_integer_div_ext(obj, &block)
block.using(IntegerDivExt) # using IntegerDivExt in the block represented by the Proc object
obj.instance_eval(&block)
end
# necessary where blocks are defined (not where Proc#using is called)
using Proc::Refinements
p 1 / 2 #=> 0
instance_eval_with_integer_div_ext(1) do
p self / 2 #=> (1/2)
end
p 1 / 2 #=> 0
PoC implementation¶
For CRuby: https://github.com/shugo/ruby/pull/2
For JRuby: https://github.com/shugo/jruby/pull/1
Background¶
I proposed Feature #12086: using: option for instance_eval etc. before, but it has problems:
- Thread safety: The same block can be invoked with different refinements in multiple threads, so it's hard to implement method caching.
- _exec family support: {instance,class,module}_exec cannot be supported.
- Implicit use of refinements: every blocks can be used with refinements, so there was implementation difficulty in JRuby and it has usability issue in headius's opinion.
Solutions in this proposal¶
Thread safety¶
Proc#using affects the block represented by the Proc object, neither the specific Proc object nor the specific block invocation.
Method calls in a block are resolved with refinements which are used by Proc#using in the block at the time.
Once all possible refinements are used in the block, there is no need to invalidate method cache anymore.
See these tests to understand how it works.
Which refinements are used is depending on the order of Proc#using invocations until all Proc#using calls are finished, but eventually method calls in a block are resolved with the same refinements.
* _exec family support¶
Feature #12086 was an extension of _eval family, so it cannot be used with _exec family, but Proc#using is independent from _eval family, and can be used with _exec family:
def instance_exec_with_integer_div_ext(obj, *args, &block)
block.using(IntegerDivExt)
obj.instance_exec(*args, &block)
end
using Proc::Refinements
p 1 / 2 #=> 0
instance_exec_with_integer_div_ext(1, 2) do |other|
p self / other #=> (1/2)
end
p 1 / 2 #=> 0
Implicit use of refinements¶
Proc#using can be used only if using Proc::Refinements
is called in the scope of the block represented by the Proc object.
Otherwise, a RuntimeError is raised.
There are two reasons:
- JRuby creates a special CallSite for refinements at compile-time only when
using
is called at the scope. - When reading programs, it may help understanding behavior. IMHO, it may be unnecessary if libraries which uses Proc#using are well documented.
Proc::Refinements
is a dummy module, and has no actual refinements.