From: shugo@... Date: 2019-12-31T02:09:07+00:00 Subject: [ruby-core:96604] [Ruby master Feature#16461] Proc#using Issue #16461 has been updated by shugo (Shugo Maeda). Eregon (Benoit Daloze) wrote: > This still has the problem that it mutates the Proc, what should happen if another Thread concurrently does `block.using(OtherRefinement)` ? It doesn't mutate the Proc, but the block, and if OtherRefinement is activated before the block invocation, both refinements are activated. If the refinements are conflicted, it doesn't work, but I don't think such a situation happens in real world use cases. > Also it still seems inefficient, at least if there are `block.using(refinement)` with different refinements. > IMHO refinements should remain lexically scoped, so for a given call site it always means the same set of refinements. > > That's how it's implemented in TruffleRuby, we use the guarantee that at a given call site refinements cannot change. > So then we can just consider the used refinements during the first method lookup, and after don't need to check anything extra, which mean zero overhead for refinements on peak performance. > There is no special detection for `using`, every call site considers refinements during method lookup. Is it hard to invalidate cache only when new refinements are activated by Proc#using? In my proposal refinements are activated per block (not per Proc), so the refinements activated in a given call site are eventually same. Or it may be possible to prohibit adding *new* refinements after the first call of the block. > For your example above, I think `using IntegerDivExt` at the top level would be much clearer. > Do you have a motivating example where that approach is much better than existing refinements? `using IntegerDivExt` activates refinements in that entire scope, but I'd like to narrow the scope to blocks for DSLs which refine built-in classes (e.g, https://github.com/amatsuda/activerecord-refinements). Allowing `using` in blocks may be used instead, but it's too verbose for such DSLs. ---------------------------------------- Feature #16461: Proc#using https://bugs.ruby-lang.org/issues/16461#change-83572 * Author: shugo (Shugo Maeda) * Status: Open * Priority: Normal * Assignee: * Target version: 2.8 ---------------------------------------- ## Overview I propose Proc#using to support block-level refinements. ```ruby 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.](https://bugs.ruby-lang.org/issues/12086) 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](https://github.com/shugo/ruby/pull/2/commits/1c922614ad7d1fb43b73e195348c81da7a4546ef) 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](https://bugs.ruby-lang.org/issues/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: ```ruby 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. -- https://bugs.ruby-lang.org/ Unsubscribe: