Issue #17474 has been updated by Eregon (Benoit Daloze).


marcandre (Marc-Andre Lafortune) wrote in #note-9:
> Moreover, constants would ideally use `private_constant`, making the resulting code very verbose.

Doesn't `private_constant` (no arguments) work? (if not, sounds worth adding)

If we'd want to do this, then deep freezing seems essential.
Execute once and cache is an anti-pattern if it returns something mutable.

If the once-expression can capture state, it's also a problem. Here is a trivial example:
```ruby
def m(i)
  $("foo#{i}")
end

m(1) # => "foo1"
m(2) # => "foo1" (BUG)
```

The original example is not compelling to me, because it's something a JIT can easily fold.
And escape analysis can avoid allocating those Ranges.
So basically we'd manually encode in the source code something a JIT can already do.
Such code will anyway always run quite a bit faster with a JIT able to see through these operations, with or without once-expression.

Overall, it feels un-Ruby to me. It's a manual low-level optimization hack, just to avoid the need to name a constant.


----------------------------------------
Feature #17474: Interpreting constants at compile time
https://bugs.ruby-lang.org/issues/17474#change-89604

* Author: jzakiya (Jabari Zakiya)
* Status: Open
* Priority: Normal
----------------------------------------
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/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>