https://redmine.ruby-lang.org/https://redmine.ruby-lang.org/favicon.ico?17113305112018-12-08T13:58:22ZRuby Issue Tracking SystemRuby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=754892018-12-08T13:58:22Zshevegen (Robert A. Heiler)shevegen@gmail.com
<ul></ul><p>About the name - some suggestions if only to show which variants may be<br>
better than others. :)</p>
<p>frozen: true # could say "ruby, freeze as much as you possibly can!"</p>
<p>In other words, that setting/flag could mean that strings, hashes and arrays<br>
are frozen (if the above is approved).</p>
<p>Or:</p>
<pre><code>frozen_string_array_hash_literals: true
frozen_string_hash_array_literals: true
frozen_three_literals: true
</code></pre>
<p>I am not saying any of these names are good names. Just putting them down. :)</p>
<pre><code>frozen_literals: true
</code></pre>
<p>I think one advantage for frozen_literals is that it is short. People could<br>
use that in .rb files too.</p>
<p>Another approach could of course also be to only allow strings to be frozen<br>
(which will become the default in ruby 3.x if I remember correctly); and not<br>
allow a change of hash and arrays in a .rb file, but to allow it for the<br>
suggestion above, e. g. in RubyVM.</p>
<p>One could also split the above up into hash and array as separate calls,<br>
so rather than:</p>
<pre><code>RubyVM::InstructionSequence.compile(<<-eocode, __FILE__, nil, 0, frozen_string_literal: true, frozen_hash_and_array_literal: true)
</code></pre>
<p>to have:</p>
<pre><code>RubyVM::InstructionSequence.compile(<<-eocode, __FILE__, nil, 0, frozen_string_literal: true, frozen_hash_literal: true, frozen_array_literal: true)
</code></pre>
<p>I mention the last primarily because we already have frozen string literals; so for<br>
consistency it may be useful to have it split up into hash and arrays too. If this<br>
is too cumbersome to use three separate calls, then one could always use keys that<br>
unify these, such as in the example given by Aaron:</p>
<pre><code>frozen_hash_and_array_literal: true
</code></pre>
<p>could also include Strings into it:</p>
<pre><code>frozen_string_and_hash_and_array_literal: true
</code></pre>
<p>It's a bit cumbersome though. Perhaps a simple variant may then be:</p>
<pre><code>frozen: true
frozen_literal: true
frozen_literals: true
</code></pre>
<p>This could then mean to "freeze all that can be frozen".</p>
<p>Anyway; I think the first step is to ask matz about the functionality<br>
itself, and then see which API may be best if this is approved.</p>
<p>Personally I think "frozen_literals" would be ok, even if the name<br>
may not be 100% "perfect". Ruby is not necessarily perfect in all<br>
names e. g. I remember a few folks thinking that the word constant<br>
implies "can not be changed", which may be the accurate meaning,<br>
but from within ruby itself, I think it is perfectly fine to let<br>
people change constants if they want to (e. g. the philosophy of<br>
ruby to be practical and useful).</p> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=754902018-12-08T17:58:07ZEregon (Benoit Daloze)
<ul></ul><p>One alternative idea which would achieve a similar goal is having #deep_freeze on core types, and recognizing <code>{ 'a' => ['b', { 'c' => 'd' }] }.deep_freeze</code>.<br>
This would be more general, since it would also work for freezing non-constant/non-literal data structures.</p>
<p>I would think many <code>[]</code> and <code>{}</code> are meant to be mutated, so it seems frozen Array/Hash literals them would rather be the exception than the norm (which is less clear for Strings, so I think there the magic comment makes sense).</p> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=755092018-12-10T00:55:16Zshyouhei (Shyouhei Urabe)shyouhei@ruby-lang.org
<ul></ul><p>Off topic:</p>
<p>Eregon (Benoit Daloze) wrote:</p>
<blockquote>
<p>One alternative idea which would achieve a similar goal is having #deep_freeze on core types, and recognizing <code>{ 'a' => ['b', { 'c' => 'd' }] }.deep_freeze</code>.<br>
This would be more general, since it would also work for freezing non-constant/non-literal data structures.</p>
</blockquote>
<p>Object#deep_freeze can be written without any extensions right now, like this: <a href="https://github.com/shyouhei/optdown/blob/master/lib/optdown/deeply_frozen.rb" class="external">https://github.com/shyouhei/optdown/blob/master/lib/optdown/deeply_frozen.rb</a><br>
I heard this trick from <a class="user active user-mention" href="https://redmine.ruby-lang.org/users/271">@akr (Akira Tanaka)</a> .</p> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=755512018-12-10T13:13:10ZEregon (Benoit Daloze)
<ul></ul><p>On Mon, Dec 10, 2018 at 1:55 AM <a href="mailto:shyouhei@ruby-lang.org" class="email">shyouhei@ruby-lang.org</a> wrote:</p>
<blockquote>
<p>Off topic:</p>
<p>Eregon (Benoit Daloze) wrote:</p>
<blockquote>
<p>One alternative idea which would achieve a similar goal is having #deep_freeze on core types, and recognizing <code>{ 'a' => ['b', { 'c' => 'd' }] }.deep_freeze</code>.<br>
This would be more general, since it would also work for freezing non-constant/non-literal data structures.</p>
</blockquote>
<p>Object#deep_freeze can be written without any extensions right now, like this: <a href="https://github.com/shyouhei/optdown/blob/master/lib/optdown/deeply_frozen.rb" class="external">https://github.com/shyouhei/optdown/blob/master/lib/optdown/deeply_frozen.rb</a><br>
I heard this trick from <a class="user active user-mention" href="https://redmine.ruby-lang.org/users/271">@akr (Akira Tanaka)</a> .</p>
</blockquote>
<p>Interesting code :)<br>
But also inefficient of course, making extra copies (changing identity) and allocations.</p> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=755572018-12-10T20:10:50Ztenderlovemaking (Aaron Patterson)tenderlove@ruby-lang.org
<ul></ul><p>Eregon (Benoit Daloze) wrote:</p>
<blockquote>
<p>One alternative idea which would achieve a similar goal is having #deep_freeze on core types, and recognizing <code>{ 'a' => ['b', { 'c' => 'd' }] }.deep_freeze</code>.<br>
This would be more general, since it would also work for freezing non-constant/non-literal data structures.</p>
</blockquote>
<p>I thought about doing this with ".freeze", introducing a special instruction the same way we do for the <code>"string".freeze</code> optimization, but dealing with deoptization in the case someone monkey patches the method seems like a pain.</p>
<p>The reason I went this direction first is that it side steps the deoptimization problem, and if we want to support a ".deep_freeze" method later we can. I imagine the implementation would be a branch where one of the branches is the same <code>putobject</code> instruction that I have in this patch.</p>
<p>Anyway, I think the advantages of this patch are:</p>
<ol>
<li>No new methods like "deep_freeze", so you can try this with existing code</li>
<li>Any code you write that works with these flags enabled will also work with them disabled (vs deep_freeze that isn't on existing Rubys)</li>
<li>This patch should be forward compatible when we figure out what "deep_freeze" solution we're going to have in the future</li>
</ol>
<blockquote>
<p>I would think many <code>[]</code> and <code>{}</code> are meant to be mutated, so it seems frozen Array/Hash literals them would rather be the exception than the norm (which is less clear for Strings, so I think there the magic comment makes sense).</p>
</blockquote>
<p>I thought the same thing, but I'm not 100% convinced. Lots of code in Rails (as well as our application at work) is merging some kind of "default hash" like:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">method</span><span class="p">(</span><span class="n">some_hash</span><span class="p">)</span>
<span class="n">some_hash</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">:defaults</span> <span class="o">=></span> <span class="s1">'thing'</span> <span class="p">}.</span><span class="nf">merge</span><span class="p">(</span><span class="n">some_hash</span><span class="p">)</span>
<span class="k">end</span>
</code></pre>
<p>I'll try to get some numbers on this though. :D</p> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=756172018-12-12T21:35:00Zshan (Shannon Skipper)
<ul></ul><p>I had the same thought as shevy, that it'd be nice to have a:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># frozen_literals: true</span>
</code></pre>
<p>It might also be worth considering a separate <code>frozen_array_literal</code> and <code>frozen_hash_literal</code>. I have files where I'd initially only want to enable one of the above.</p> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=756572018-12-13T08:47:34Zjanfri (Jan Friedrich)janfri26@gmail.com
<ul></ul><p>shan (Shannon Skipper) wrote:</p>
<blockquote>
<p>I had the same thought as shevy, that it'd be nice to have a:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># frozen_literals: true</span>
</code></pre>
<p>It might also be worth considering a separate <code>frozen_array_literal</code> and <code>frozen_hash_literal</code>. I have files where I'd initially only want to enable one of the above.</p>
</blockquote>
<p>+1</p> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=756842018-12-14T19:51:36Zdevpolish (Nardo Nykolyszyn)
<ul></ul><p>I've been waiting this for a while. I'm completely agree.</p> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=756982018-12-15T13:27:02ZEregon (Benoit Daloze)
<ul></ul><p>tenderlovemaking (Aaron Patterson) wrote:</p>
<blockquote>
<p>I thought about doing this with ".freeze", introducing a special instruction the same way we do for the <code>"string".freeze</code> optimization, but dealing with deoptization in the case someone monkey patches the method seems like a pain.</p>
</blockquote>
<p>I think it might semantically make some sense to ignore monkey-patching of <code>.freeze</code> (and <code>.deep_freeze</code>) for calls on literals, which would then avoid needing deoptimization for this (although deoptimization would be a flag check + the current logic as fallback, it doesn't seem so bad).<br>
It's somewhat similar to String literals not calling String#initialize for instance (same for Array, Hash).<br>
And it's probably a bad idea to override #freeze in the hope it would be used on frozen literals anyway, as of course this would only work if the monkey-patch is reliably loaded before everything else.</p>
<p>Another issue is it's quite ugly to opt out of frozen array/hash literals, if a mutable copy is wanted:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># frozen_hash_and_array_literal: true</span>
<span class="n">my_mutable_data</span> <span class="o">=</span> <span class="p">{</span> <span class="s1">'a'</span> <span class="o">=></span> <span class="p">[</span><span class="s1">'b'</span><span class="p">,</span> <span class="p">{</span> <span class="s1">'c'</span> <span class="o">=></span> <span class="s1">'d'</span> <span class="p">}.</span><span class="nf">dup</span><span class="p">].</span><span class="nf">dup</span> <span class="p">}.</span><span class="nf">dup</span>
</code></pre>
<p>That's why I think <code>deep_freeze</code> would better express the intent in some cases, and be finer-grained:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">MY_CONSTANT</span> <span class="o">=</span> <span class="p">{</span> <span class="s1">'a'</span> <span class="o">=></span> <span class="p">[</span><span class="s1">'b'</span><span class="p">,</span> <span class="p">{</span> <span class="s1">'c'</span> <span class="o">=></span> <span class="s1">'d'</span> <span class="p">}]</span> <span class="p">}.</span><span class="nf">deep_freeze</span>
<span class="n">my_mutable_data</span> <span class="o">=</span> <span class="p">{</span> <span class="s1">'a'</span> <span class="o">=></span> <span class="p">[</span><span class="s1">'b'</span><span class="p">,</span> <span class="p">{</span> <span class="s1">'c'</span> <span class="o">=></span> <span class="s1">'d'</span> <span class="p">}]</span> <span class="p">}</span>
<span class="p">{</span> <span class="ss">:defaults</span> <span class="o">=></span> <span class="s1">'thing'</span> <span class="p">}.</span><span class="nf">deep_freeze</span><span class="p">.</span><span class="nf">merge</span><span class="p">(</span><span class="n">some_hash</span><span class="p">)</span>
</code></pre>
<p>and this would work regardless of what is the value to freeze (but only avoid allocations if it's all literals, otherwise a constant must be used).</p>
<p>Furthermore, frozen literals don't allow composition or extraction in different constants:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># frozen_hash_and_array_literal: true</span>
<span class="no">MY_KEY</span> <span class="o">=</span> <span class="s1">'a'</span>
<span class="no">MY_CONSTANT</span> <span class="o">=</span> <span class="p">{</span> <span class="no">MY_KEY</span> <span class="o">=></span> <span class="p">[</span><span class="s1">'b'</span><span class="p">,</span> <span class="p">{</span> <span class="s1">'c'</span> <span class="o">=></span> <span class="s1">'d'</span> <span class="p">}]</span> <span class="p">}</span> <span class="c1"># Not frozen, and breaks referential transparency</span>
<span class="no">MY_CONSTANT</span> <span class="o">=</span> <span class="p">{</span> <span class="no">MY_KEY</span> <span class="o">=></span> <span class="p">[</span><span class="s1">'b'</span><span class="p">,</span> <span class="p">{</span> <span class="s1">'c'</span> <span class="o">=></span> <span class="s1">'d'</span> <span class="p">}]</span> <span class="p">}.</span><span class="nf">deep_freeze</span> <span class="c1"># Works</span>
</code></pre>
<p>OTOH, "string".freeze has shown the magic comment is much nicer in many cases than adding "string".freeze in many places (at the price of making a mutable String not so nice, but those seem rarer).<br>
It would be interesting to get an idea of what's a typical ratio of immutable/mutable Array and Hash literals, and what converting a codebase to use frozen Array/Hash literals would look like.</p> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=757632018-12-18T17:08:27ZAnonymous
<ul><li><strong>File</strong> <i>frequency-2.rb</i> added</li><li><strong>File</strong> <i>frequency-1.rb</i> added</li></ul> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=757642018-12-18T17:13:39ZAnonymous
<ul><li><strong>File</strong> deleted (<del><i>0001-Add-compile-options-for-freezing-hash-and-array-lite.patch</i></del>)</li><li><strong>File</strong> deleted (<del><i>frequency-1.rb</i></del>)</li><li><strong>File</strong> deleted (<del><i>frequency-2.rb</i></del>)</li></ul> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=757652018-12-18T17:14:59ZAnonymous
<ul><li><strong>File</strong> <i>frequency-1.rb</i> added</li><li><strong>File</strong> <i>frequency-2.rb</i> added</li></ul><blockquote>
<p>I would like to add VM compilation options to freeze array and hash literals. For example:</p>
</blockquote>
<p>My two samples:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="k">def</span> <span class="nf">frequency</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">prepare_string</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="n">text</span><span class="p">.</span><span class="nf">gsub!</span><span class="p">(</span><span class="s2">","</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span>
<span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="s2">" "</span><span class="p">)</span> <span class="c1">#</span>
<span class="k">end</span> <span class="c1"># <-- freeze the result and return it</span>
<span class="c1">#</span>
<span class="k">def</span> <span class="nf">sort_frequency_report</span><span class="p">(</span><span class="n">fq</span><span class="p">)</span>
<span class="n">fq</span><span class="p">.</span><span class="nf">to_h</span>
<span class="p">.</span><span class="nf">sort_by</span><span class="p">{</span><span class="o">|</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="o">|</span> <span class="n">v</span> <span class="p">}</span>
<span class="p">.</span><span class="nf">reverse</span>
<span class="p">.</span><span class="nf">to_h</span> <span class="c1">#</span>
<span class="k">end</span> <span class="c1"># <-- freeze the result and return it</span>
<span class="c1">#</span>
<span class="n">word_list</span> <span class="o">=</span> <span class="n">prepare_string</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="n">fq</span> <span class="o">=</span> <span class="no">Hash</span><span class="p">.</span><span class="nf">new</span><span class="p">()</span>
<span class="k">for</span> <span class="n">word</span> <span class="k">in</span> <span class="n">word_list</span>
<span class="k">if</span> <span class="n">fq</span><span class="p">.</span><span class="nf">has_key?</span><span class="p">(</span><span class="n">word</span><span class="p">)</span>
<span class="n">fq</span><span class="p">[</span><span class="n">word</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">else</span>
<span class="n">fq</span><span class="p">[</span><span class="n">word</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># </span>
<span class="n">sort_frequency_report</span><span class="p">(</span><span class="n">fq</span><span class="p">)</span> <span class="c1"># <-- freeze the result and return it</span>
<span class="k">end</span> <span class="c1">#</span>
<span class="nb">p</span> <span class="n">frequency</span><span class="p">(</span><span class="s2">"text text text text,
text txet text text,
text text text"</span><span class="p">)</span>
</code></pre>
<p>And:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="k">def</span> <span class="nf">frequency</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">prepare_string</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="n">text</span><span class="p">.</span><span class="nf">gsub!</span><span class="p">(</span><span class="s2">","</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span>
<span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="s2">" "</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">sort_frequency_report</span><span class="p">(</span><span class="n">fq</span><span class="p">)</span>
<span class="c1"># #</span>
<span class="c1"># fq = Hash.new() # Why?!.. is it correct?..</span>
<span class="c1"># #</span>
<span class="n">fq</span><span class="p">.</span><span class="nf">to_h</span>
<span class="p">.</span><span class="nf">sort_by</span><span class="p">{</span><span class="o">|</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="o">|</span> <span class="n">v</span> <span class="p">}</span>
<span class="p">.</span><span class="nf">reverse</span>
<span class="p">.</span><span class="nf">to_h</span>
<span class="k">end</span>
<span class="n">word_list</span> <span class="o">=</span> <span class="n">prepare_string</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="n">fq</span> <span class="o">=</span> <span class="no">Hash</span><span class="p">.</span><span class="nf">new</span><span class="p">()</span>
<span class="k">for</span> <span class="n">word</span> <span class="k">in</span> <span class="n">word_list</span>
<span class="k">if</span> <span class="n">fq</span><span class="p">.</span><span class="nf">has_key?</span><span class="p">(</span><span class="n">word</span><span class="p">)</span>
<span class="n">fq</span><span class="p">[</span><span class="n">word</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">else</span>
<span class="n">fq</span><span class="p">[</span><span class="n">word</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">sort_frequency_report</span><span class="p">(</span><span class="n">fq</span><span class="p">)</span>
<span class="k">end</span>
<span class="nb">p</span> <span class="n">frequency</span><span class="p">(</span><span class="s2">"text text text text,
text txet text text,
text text text"</span><span class="p">)</span>
</code></pre> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=757662018-12-18T17:20:06ZAnonymous
<ul><li><strong>File</strong> <i>frequency-1.rb</i> added</li><li><strong>File</strong> <i>frequency-2.rb</i> added</li><li><strong>File</strong> deleted (<del><i>frequency-1.rb</i></del>)</li><li><strong>File</strong> deleted (<del><i>frequency-2.rb</i></del>)</li></ul> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=757672018-12-18T17:21:03ZAnonymous
<ul><li><strong>File</strong> deleted (<del><i>frequency-1.rb</i></del>)</li><li><strong>File</strong> deleted (<del><i>frequency-2.rb</i></del>)</li></ul> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=763232019-01-15T00:15:58Ztenderlovemaking (Aaron Patterson)tenderlove@ruby-lang.org
<ul></ul><p>Eregon (Benoit Daloze) wrote:</p>
<blockquote>
<p>tenderlovemaking (Aaron Patterson) wrote:</p>
<blockquote>
<p>I thought about doing this with ".freeze", introducing a special instruction the same way we do for the <code>"string".freeze</code> optimization, but dealing with deoptization in the case someone monkey patches the method seems like a pain.</p>
</blockquote>
<p>I think it might semantically make some sense to ignore monkey-patching of <code>.freeze</code> (and <code>.deep_freeze</code>) for calls on literals, which would then avoid needing deoptimization for this (although deoptimization would be a flag check + the current logic as fallback, it doesn't seem so bad).<br>
It's somewhat similar to String literals not calling String#initialize for instance (same for Array, Hash).<br>
And it's probably a bad idea to override #freeze in the hope it would be used on frozen literals anyway, as of course this would only work if the monkey-patch is reliably loaded before everything else.</p>
<p>Another issue is it's quite ugly to opt out of frozen array/hash literals, if a mutable copy is wanted:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># frozen_hash_and_array_literal: true</span>
<span class="n">my_mutable_data</span> <span class="o">=</span> <span class="p">{</span> <span class="s1">'a'</span> <span class="o">=></span> <span class="p">[</span><span class="s1">'b'</span><span class="p">,</span> <span class="p">{</span> <span class="s1">'c'</span> <span class="o">=></span> <span class="s1">'d'</span> <span class="p">}.</span><span class="nf">dup</span><span class="p">].</span><span class="nf">dup</span> <span class="p">}.</span><span class="nf">dup</span>
</code></pre>
<p>That's why I think <code>deep_freeze</code> would better express the intent in some cases, and be finer-grained:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">MY_CONSTANT</span> <span class="o">=</span> <span class="p">{</span> <span class="s1">'a'</span> <span class="o">=></span> <span class="p">[</span><span class="s1">'b'</span><span class="p">,</span> <span class="p">{</span> <span class="s1">'c'</span> <span class="o">=></span> <span class="s1">'d'</span> <span class="p">}]</span> <span class="p">}.</span><span class="nf">deep_freeze</span>
<span class="n">my_mutable_data</span> <span class="o">=</span> <span class="p">{</span> <span class="s1">'a'</span> <span class="o">=></span> <span class="p">[</span><span class="s1">'b'</span><span class="p">,</span> <span class="p">{</span> <span class="s1">'c'</span> <span class="o">=></span> <span class="s1">'d'</span> <span class="p">}]</span> <span class="p">}</span>
<span class="p">{</span> <span class="ss">:defaults</span> <span class="o">=></span> <span class="s1">'thing'</span> <span class="p">}.</span><span class="nf">deep_freeze</span><span class="p">.</span><span class="nf">merge</span><span class="p">(</span><span class="n">some_hash</span><span class="p">)</span>
</code></pre>
</blockquote>
<p>Yes, I totally agree. I think <code>deep_freeze</code> is going to be necessary for adoption of Guilds, and the code I'm proposing in this patch would be an optimization of the <code>deep_freeze</code> call on a literal.</p>
<blockquote>
<p>and this would work regardless of what is the value to freeze (but only avoid allocations if it's all literals, otherwise a constant must be used).</p>
<p>Furthermore, frozen literals don't allow composition or extraction in different constants:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># frozen_hash_and_array_literal: true</span>
<span class="no">MY_KEY</span> <span class="o">=</span> <span class="s1">'a'</span>
<span class="no">MY_CONSTANT</span> <span class="o">=</span> <span class="p">{</span> <span class="no">MY_KEY</span> <span class="o">=></span> <span class="p">[</span><span class="s1">'b'</span><span class="p">,</span> <span class="p">{</span> <span class="s1">'c'</span> <span class="o">=></span> <span class="s1">'d'</span> <span class="p">}]</span> <span class="p">}</span> <span class="c1"># Not frozen, and breaks referential transparency</span>
<span class="no">MY_CONSTANT</span> <span class="o">=</span> <span class="p">{</span> <span class="no">MY_KEY</span> <span class="o">=></span> <span class="p">[</span><span class="s1">'b'</span><span class="p">,</span> <span class="p">{</span> <span class="s1">'c'</span> <span class="o">=></span> <span class="s1">'d'</span> <span class="p">}]</span> <span class="p">}.</span><span class="nf">deep_freeze</span> <span class="c1"># Works</span>
</code></pre>
<p>OTOH, "string".freeze has shown the magic comment is much nicer in many cases than adding "string".freeze in many places (at the price of making a mutable String not so nice, but those seem rarer).<br>
It would be interesting to get an idea of what's a typical ratio of immutable/mutable Array and Hash literals, and what converting a codebase to use frozen Array/Hash literals would look like.</p>
</blockquote>
<p>Right. I'm not proposing adding the magic comment at all, just adding a parameter to the ISeq constructor that will allow you to "deep freeze" literals in the code passed to ISeq#new.</p> Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalshttps://redmine.ruby-lang.org/issues/15393?journal_id=841182020-01-30T16:00:30ZEregon (Benoit Daloze)
<ul><li><strong>Related to</strong> <i><a class="issue tracker-2 status-1 priority-4 priority-default" href="/issues/16600">Feature #16600</a>: Optimized opcodes for frozen arrays and hashes literals</i> added</li></ul>