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-Status: No, score=-3.8 required=3.0 tests=AWL,BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, SPF_HELO_NONE,SPF_PASS,UNPARSEABLE_RELAY 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 506531F4B4 for ; Sun, 27 Dec 2020 22:28:20 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 2A03F120AD2; Mon, 28 Dec 2020 07:27:31 +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 D0AD8120ACC for ; Mon, 28 Dec 2020 07:27:28 +0900 (JST) Received: by filterdrecv-p3mdw1-7474cd8bfd-58ncc with SMTP id filterdrecv-p3mdw1-7474cd8bfd-58ncc-19-5FE90A79-7 2020-12-27 22:28:09.311622995 +0000 UTC m=+1466763.539594681 Received: from herokuapp.com (unknown) by ismtpd0134p1mdw1.sendgrid.net (SG) with ESMTP id OVUSdxeqTv6dBIEn8_Hxtw for ; Sun, 27 Dec 2020 22:28:09.189 +0000 (UTC) Date: Sun, 27 Dec 2020 22:28:09 +0000 (UTC) From: marcandre-ruby-core@marc-andre.ca Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 77661 X-Redmine-Project: ruby-master X-Redmine-Issue-Tracker: Feature X-Redmine-Issue-Id: 17474 X-Redmine-Issue-Author: jzakiya X-Redmine-Issue-Assignee: matz X-Redmine-Sender: marcandre 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?6=2FIMxCQLDposcQf5wmbDAtfaKduBAO0bKyhL3BGZtMQ5q7K2TvpbN6A7JIyt9E?= =?us-ascii?Q?aO7AKARedX76oYKcmVhGbzyIRfgTt8cugfUifgn?= =?us-ascii?Q?E0Lju6MZw1HY2w7Rv2VJ9=2Ft5VfOEYqUGtluwdIk?= =?us-ascii?Q?1tlL7Ku2qjp+19i5tgzc85b1IbnURlpCaP2FIYU?= =?us-ascii?Q?z53Y+g5SCrMeBwzOqdtFNzsvjrw3j+JUwAzXLJ8?= =?us-ascii?Q?coUBUU=2FJGLEQMhxyofIk9IBKGMzFnCA6DpelVi?= To: ruby-core@ruby-lang.org X-Entity-ID: b/2+PoftWZ6GuOu3b0IycA== X-ML-Name: ruby-core X-Mail-Count: 101759 Subject: [ruby-core:101759] [Ruby master Feature#17474] Interpreting constants at compile time 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 #17474 has been updated by marcandre (Marc-Andre Lafortune). Assignee set to matz (Yukihiro Matsumoto) Status changed from Rejected to Open tldnr; I feel that the possibility to have "inline shareable constant expressions" could improve expressiveness and allow for easier optimizations. > There is no "compile time" for Ruby, and there is no way to execute arbitrary Ruby code at any other time than runtime This is true, but there is "parse time" and there is also "first time" like `/...#{run_once}.../o`. I believe the request is for something similar to the "o" mode, but for general expressions. I'll note that the code example above creates Range objects every time it runs too, while these would gain in being created only once. The example given would need about 20 constants, some of them being difficult to name. Moreover, constants would ideally use `private_constant`, making the resulting code very verbose. Finally, while the above constants are mostly Integer and Range (frozen), there is one Array (can be frozen implicitly with `#shareable_constant_value) and there could be user types (e.g. `Set`) that can only implicitly frozen with experimental mode currently. If one does not freeze the constant, then the code will run, until someone tries it from a non-main Ractor. In short, I find the use-case compelling, and the current solution less than ideal: ```ruby MILLIONS = 10**6 ... 10**7 TENS_OF_MILLIONS = 10**7 ... 10**8 # ... (what is good name for 16 * 10**10?? ) private_constant :MILLIONS, :TENS_OF_MILLIONS #, ... def select_pg(endnum, startnum) # ... end ``` I've seen multiple cases of code with inline `[...]` that were constant and would have benefited from extraction to a named constant, at the cost of verbosity. I've also requested a dedicated syntax for such sets of symbols, or strings in #16994, for that purpose. ---------------------------------------- Feature #17474: Interpreting constants at compile time https://bugs.ruby-lang.org/issues/17474#change-89586 * Author: jzakiya (Jabari Zakiya) * Status: Open * Priority: Normal * Assignee: matz (Yukihiro Matsumoto) ---------------------------------------- Ruby has borrowed concepts/idioms from allot of languages. I am proposing borrowing a feature from Forth to provide for compile time interpretation of Constants. This should make executed code faster|efficient, while maintaining source code brevity|clarity. Below is actual code used in a large rubygem I have. To develop this method, I had to do allot of test runs to determine the range values. Once found, these values don't change, but I just kept the computed forms of the values, in case I want to upgrade them. In Forth I can interpret those expressions that result in constants, which will be compiled as single values for run time. See wikeipedia article on Forth below starting at **Mixing states of compiling and interpreting**. https://en.wikipedia.org/wiki/Forth_(programming_language) Forth was designed for, and is still used most frequently, in hardware controllers, and with microprocessors. IMHO this feature would also make MRuby more code efficient and faster for this domain too, and IOT devices. Below is an example of real code that would benefit from this. While this example would result in numerical constant, string constants could also be interpreted. ``` def select_pg(endnum, startnum) start_num = end_num end_num = endnum; start_num = startnum range = end_num - start_num pg = 5 if start_num <= Integer.sqrt(end_num) # for one array of primes upto N pg = 7 if end_num > 50 * 10**4 pg = 11 if end_num > 305 * 10**5 else # for split array cases pg = 7 if ((10**6 ... 10**7).include?(range) && start_num < 10**8) || ((10**7 ... 10**8).include?(range) && start_num < 46 * 10**8) || ((10**8 ... 10**9).include?(range) && start_num < 16 * 10**10) || (range >= 10**9 && start_num < 26 * 10**12) pg = 11 if ((10**8 ... 10**9).include?(range) && start_num < 55 * 10**7) || (range >= 10**9 && start_num < 45 * 10**9) end primes = [2, 3, 5, 7, 11, 13].select { |p| p <= pg } {primes, primes.reduce(:*)} # [excluded primes, modpg] for PG end ``` Allowing for compile time interpretation, the code could be rewritten as below. ``` def select_pg(endnum, startnum) start_num = end_num end_num = endnum; start_num = startnum range = end_num - start_num pg = 5 if start_num <= Integer.sqrt(end_num) # for one array of primes upto N pg = 7 if end_num > [50 * 10**4] pg = 11 if end_num > [305 * 10**5] else # for split array cases pg = 7 if (([10**6] ... [10**7]).include?(range) && start_num < [10**8]) || (([10**7] ... [10**8]).include?(range) && start_num < [46 * 10**8]) || (([10**8] ... [10**9]).include?(range) && start_num < [16 * 10**10])|| (range >= [10**9] && start_num < [26 * 10**12]) pg = 11 if (([10**8] ... [10**9]).include?(range) && start_num < [55 * 10**7]) || (range >= [10**9] && start_num < [45 * 10**9]) end primes = [2, 3, 5, 7, 11, 13].select { |p| p <= pg } {primes, primes.reduce(:*)} # [excluded primes, modpg] for PG end ``` This maintains the original form, so if I need/want to change the range limits again I can just change the calculation inline, without having to remember where those values came from. As 3.0 has introduced many new features and idioms, this could be introduced with no breaking change too. Old code would work as before, while new code could take advantage of this feature. Thanks is advance of giving this proposal serious consideration. -- https://bugs.ruby-lang.org/