From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS4713 221.184.0.0/13 X-Spam-Status: No, score=-4.1 required=3.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED,SPF_HELO_NONE,SPF_PASS shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from neon.ruby-lang.org (neon.ruby-lang.org [221.186.184.75]) by dcvr.yhbt.net (Postfix) with ESMTP id B60071F463 for ; Tue, 31 Dec 2019 02:09:25 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id E266012097F; Tue, 31 Dec 2019 11:09:07 +0900 (JST) Received: from xtrwkhkc.outbound-mail.sendgrid.net (xtrwkhkc.outbound-mail.sendgrid.net [167.89.16.28]) by neon.ruby-lang.org (Postfix) with ESMTPS id 3D6A8120929 for ; Tue, 31 Dec 2019 11:09:06 +0900 (JST) Received: by filterdrecv-p3mdw1-56c97568b5-72qfl with SMTP id filterdrecv-p3mdw1-56c97568b5-72qfl-18-5E0AADC3-46 2019-12-31 02:09:07.475440249 +0000 UTC m=+1214760.331295651 Received: from herokuapp.com (unknown [3.83.47.54]) by ismtpd0073p1iad2.sendgrid.net (SG) with ESMTP id czwwCEhMSG2r8ocqkDZ0Qg for ; Tue, 31 Dec 2019 02:09:07.405 +0000 (UTC) Date: Tue, 31 Dec 2019 02:09:07 +0000 (UTC) From: shugo@ruby-lang.org Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 72267 X-Redmine-Project: ruby-master X-Redmine-Issue-Id: 16461 X-Redmine-Issue-Author: shugo X-Redmine-Sender: shugo X-Mailer: Redmine X-Redmine-Host: bugs.ruby-lang.org X-Redmine-Site: Ruby Issue Tracking System X-Auto-Response-Suppress: All Auto-Submitted: auto-generated X-SG-EID: =?us-ascii?Q?A4qw=2FE7uJv6zjRy5ZA2RlSNSopH4PRxwlkqj2Qg3aeTMhEB12MTZ3nWyOin451?= =?us-ascii?Q?QwNh6vwWZFdD1Y3nyQiQ4cJOHpqbol4JltuKW7o?= =?us-ascii?Q?MQQRz1IFFYQHi7jks+Pq6v6pkpmFpviQz73qN7w?= =?us-ascii?Q?vN2wlRkI8wQr7IRT5GeJrDXhvKbTQFKuNjDx4LV?= =?us-ascii?Q?H5NwtLCc2ARlhgyqsbJ=2F060+RIJWYTQdZ5S=2FxrQ?= =?us-ascii?Q?QVnfZhDZR=2Fp3mXNjg=3D?= To: ruby-core@ruby-lang.org X-ML-Name: ruby-core X-Mail-Count: 96604 Subject: [ruby-core:96604] [Ruby master Feature#16461] Proc#using X-BeenThere: ruby-core@ruby-lang.org X-Mailman-Version: 2.1.15 Precedence: list Reply-To: Ruby developers List-Id: Ruby developers List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ruby-core-bounces@ruby-lang.org Sender: "ruby-core" 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/