From: dementiev.vm@... Date: 2020-09-05T11:05:10+00:00 Subject: [ruby-core:99938] [Ruby master Feature#16461] Proc#using Issue #16461 has been updated by palkan (Vladimir Dementyev). Disclaimer: I'm a big fan of refinements; they make Ruby more expressive (or chaotic ����) and allow developers to be more creative. shugo (Shugo Maeda) wrote in #note-5: > I admit that the behavior is complex, but DSL users need not > understand details. Unless they have to debug the code using this DSL, understanding the details leads to better code; IMO, Rails has enough magic to blow newcomers' minds. At the same time, Refinements are already too complex for most developers, even if we only deal with lexical scope and explicit activation. With `Proc#using`, we will likely make Refinements a secret knowledge only available to magicians. > I understand the intent, and not willing to have using ActiveRecord::DSL at the top-level as it would then override Symbol#== everywhere in that file. What about introducing `unusing` or similar instead? ```ruby class User < ActiveRecord::Base using ActiveRecord::WhereDSL scope :not_so_young, -> { where { :age >= 30 } } # deactivate refinement below this line unusing ActiveRecord::WhereDSL end ``` Looks a bit uglier but, IMO, much easier to understand for end-users, especially if they're familiar with refinements: we still use lexical scope, nothing new here. Another idea is to pass block to `using` (so, it's gonna be a mix of `instance_eval` + Proc#using, I think): ```ruby using ActiveRecord::WhereDSL do scope :not_so_young, -> { where { :age >= 30 } } end ``` ---------------------------------------- Feature #16461: Proc#using https://bugs.ruby-lang.org/issues/16461#change-87478 * Author: shugo (Shugo Maeda) * Status: Assigned * Priority: Normal * Assignee: matz (Yukihiro Matsumoto) * 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: