From: "shugo (Shugo Maeda) via ruby-core" Date: 2024-05-16T00:06:01+00:00 Subject: [ruby-core:117888] [Ruby master Feature#16461] Proc#using Issue #16461 has been updated by shugo (Shugo Maeda). Sorry for the delay. Eregon (Benoit Daloze) wrote in #note-10: > Reading #12086 again, I feel #12281 is a much simpler alternative (`using(refinement) { literal block }`). > Wouldn't #12281 be enough? > > `block.using(IntegerDivExt)` is mutating state and this feels too magic and dangerous to me. > I think DSL users can accept to wrap their DSL code with `using(refinement) { ... }` (or `using refinement` in a module/file). > That's actually not more verbose than `using Proc::Refinements` and I think so much clearer. It looks too redundant to me because `using` is needed for each block. > We could have additional requirements to ensure it's truly lexical and the `refinement` given to a given `using(refinement) { ... }` call site is always the same. > I think that would be good, because then we know code inside always use the given refinements, much like how `using refinement` works today. > > For the `User.where { :age > 3 }` case, if we want to avoid an extra `using(refinement) { ... }`, I think it could be fine to automatically enable refinements in that block *if* we can ensure it's still fully lexical. > Concretely that would mean for that block `{ :age > 3 }` it's always called with the same refinements, no matter where it's called from. > Attempting to use any other refinement would raise an error. > > Maybe a way to design this restriction could be to annotate the method `where` with "enables MyRefinement on the lexical block given to it". > ```ruby > class ModelClassMethods > def where(&block) > ... > block.call / yield > end > instance_method(:where).use_refinements_for_block(ActiveRecord::WhereDSL) > end > > User.where { :age > 3 } > ``` I like the idea, but if `where` is polymorphic, the block can be called with different refinements, can't it? ```ruby def age_over_three(model_class) model_class.where { :age > 3 } end ``` Do you mean to save the refinements when the block is called at the first time, and to raise an exception when the block is called with different refinements? If so, it's similar to what I meant to realize by Proc.using. ---------------------------------------- Feature #16461: Proc#using https://bugs.ruby-lang.org/issues/16461#change-108304 * Author: shugo (Shugo Maeda) * Status: Assigned * Assignee: matz (Yukihiro Matsumoto) ---------------------------------------- ## 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/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/