Project

General

Profile

Feature #16461

Proc#using

Added by shugo (Shugo Maeda) 10 months ago. Updated 4 days ago.

Status:
Assigned
Priority:
Normal
Target version:
[ruby-core:96534]

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.


Related issues

Related to Ruby master - Feature #12086: using: option for instance_eval etc.OpenActions

Also available in: Atom PDF