https://redmine.ruby-lang.org/https://redmine.ruby-lang.org/favicon.ico?17113305112018-03-06T16:09:23ZRuby Issue Tracking SystemRuby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=708152018-03-06T16:09:23ZSoilent (Konstantin x)
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/70815/diff?detail_id=48549">diff</a>)</li></ul> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=708402018-03-07T15:39:04Zshevegen (Robert A. Heiler)shevegen@gmail.com
<ul></ul><p>If I understood your proposal correctly then you want an<br>
additional way to update an existing value in a hash, correct?</p>
<p>So the comparable syntax parts would be:</p>
<pre><code>hash[:a] = hash[:a] + 42
</code></pre>
<p>versus</p>
<pre><code>hash.store(:a) { |val| val + 42 }
</code></pre>
<p>right?</p>
<p>So, if this is correct, then as I understand it, the major<br>
point of your proposal, and benefit, is that you omit querying<br>
the old value in the sedond variant, since you just operate<br>
on the block variable called "val" in your case.</p>
<p>If this is indeed the case, and that is your proposal, then I<br>
think I understand what you mean, feature-wise. In this case<br>
you skip the step where you query the old value explicitely<br>
and just tap into the block value for making a modification.</p>
<p>I think this is ok.</p>
<p>It follows my own "philosophy" of "do not make me think" or<br>
"make me think less". :D</p>
<p>I have no idea how matz feels about it; perhaps someone could<br>
suggest it in the upcoming ruby developer meeting in ~a<br>
week or so.</p>
<p>The current documentation for Hash#store can be found at:</p>
<p><a href="https://docs.ruby-lang.org/en/2.5.0/Hash.html#method-i-store" class="external">https://docs.ruby-lang.org/en/2.5.0/Hash.html#method-i-store</a></p> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=708412018-03-07T15:44:35ZEregon (Benoit Daloze)
<ul></ul><p>What should happen if the given key doesn't exist in Hash?<br>
This looks like a compute-if-present operation.</p> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=708422018-03-07T16:19:13ZHanmac (Hans Mackowiak)hanmac@gmx.de
<ul></ul><pre><code class="ruby syntaxhl" data-language="ruby"><span class="nb">hash</span><span class="p">.</span><span class="nf">transform_values</span><span class="p">(</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:b</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">val</span><span class="o">|</span> <span class="n">val</span> <span class="o">+</span> <span class="mi">42</span> <span class="p">}</span>
<span class="nb">hash</span><span class="p">[</span><span class="ss">:a</span><span class="p">]</span> <span class="c1">#=> 44</span>
</code></pre>
<p>what about the b key? should it:<br>
a) throw exception<br>
b) gives <code>nil</code> to the block ? which your code would be an <code>NoMethod + for nil</code><br>
c) will be skipped</p> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=708432018-03-07T16:21:32ZSoilent (Konstantin x)
<ul></ul><p>Hi Robert,</p>
<p>Thank you for your reply. You understood everything correctly.</p>
<p>Also, I might be wrong, but it seems to me that in the following case</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="nb">hash</span><span class="p">[</span><span class="ss">:a</span><span class="p">]</span> <span class="o">=</span> <span class="nb">hash</span><span class="p">[</span><span class="ss">:a</span><span class="p">]</span> <span class="o">+</span> <span class="mi">42</span>
</code></pre>
<p>Ruby VM will look up the key twice. The proposed method should eliminate the second lookup in this case.</p> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=708442018-03-07T16:24:52Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p>Maybe a bit off-topic, but I experimented with same ideas in <a href="https://github.com/zverok/hm" class="external">hm</a> gem. It allows code like this:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Hm</span><span class="p">(</span><span class="nb">hash</span><span class="p">)</span>
<span class="p">.</span><span class="nf">transform_values</span><span class="p">(</span><span class="ss">:a</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">val</span><span class="o">|</span> <span class="n">val</span> <span class="o">+</span> <span class="mi">42</span> <span class="p">}</span>
<span class="p">.</span><span class="nf">to_h</span>
</code></pre>
<p>After trying several approaches in production, the design decision I've made about not found keys is simply ignore them.</p> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=708452018-03-07T16:25:28ZSoilent (Konstantin x)
<ul></ul><p>Eregon (Benoit Daloze) wrote:</p>
<blockquote>
<p>What should happen if the given key doesn't exist in Hash?<br>
This looks like a compute-if-present operation.</p>
</blockquote>
<p>Good question, thank you. I think, the result of default_proc or the default value should be yielded.</p> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=708472018-03-07T16:40:18ZSoilent (Konstantin x)
<ul></ul><p>Hanmac (Hans Mackowiak) wrote:</p>
<blockquote>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="nb">hash</span><span class="p">.</span><span class="nf">transform_values</span><span class="p">(</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:b</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">val</span><span class="o">|</span> <span class="n">val</span> <span class="o">+</span> <span class="mi">42</span> <span class="p">}</span>
<span class="nb">hash</span><span class="p">[</span><span class="ss">:a</span><span class="p">]</span> <span class="c1">#=> 44</span>
</code></pre>
<p>what about the b key? should it:<br>
a) throw exception<br>
b) gives <code>nil</code> to the block ? which your code would be an <code>NoMethod + for nil</code><br>
c) will be skipped</p>
</blockquote>
<p>Thanks for the question.</p>
<p>I think that <code>hash.store(:b)</code> should yield the default value if the key does not exist, i.e. option b.<br>
But in case of <code>hash.transform_values(:a, :b)</code>, when we want to update several keys, it is best to skip non-existent keys (option c)</p> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=708582018-03-08T04:45:52Zsawa (Tsuyoshi Sawada)
<ul></ul><p>Why not write <code>hash[:a]+= 42</code>?</p> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=708962018-03-08T08:44:25ZSoilent (Konstantin x)
<ul></ul><p>sawa (Tsuyoshi Sawada) wrote:</p>
<blockquote>
<p>Why not write <code>hash[:a]+= 42</code>?</p>
</blockquote>
<p>Good point, but this works only for arithmetic operators (and also does 2 key lookups). Consider another example <code>hash.store(:time) { |ts| Time.parse(ts) }</code></p> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=709042018-03-08T09:31:53Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul></ul><p>I think it is not so simple to optimize the double lookup by this API. Consider:</p>
<pre><code>hash.store(:a) {|val| 10000.times {|n| hash[n] = true }; val + 42 }
</code></pre>
<p>or:</p>
<pre><code>hash.store(:a) {|val| hash.rehash; val + 42 }
</code></pre>
<p>We need to keep a flag if rehash occurred or not during the block executed.</p> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=709072018-03-08T10:24:02ZEregon (Benoit Daloze)
<ul></ul><p>Soilent (Konstantin x) wrote:</p>
<blockquote>
<p>Consider another example <code>hash.store(:time) { |ts| Time.parse(ts) }</code></p>
</blockquote>
<p>That looks weird to me.<br>
Either the Hash is caching String to Time, and then it should use</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Hash</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">h</span><span class="p">,</span><span class="n">k</span><span class="o">|</span> <span class="n">h</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">k</span><span class="p">)</span> <span class="p">}</span>
</code></pre>
<p>or it contains other data and then there seems to be little reason to first store a String for key :time and then only later parse it to a Time instance.</p> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=709082018-03-08T10:29:24ZEregon (Benoit Daloze)
<ul></ul><p>mame (Yusuke Endoh) wrote:</p>
<blockquote>
<p>We need to keep a flag if rehash occurred or not during the block executed.</p>
</blockquote>
<p>Also, what should happen with:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="nb">hash</span><span class="p">.</span><span class="nf">store</span><span class="p">(</span><span class="ss">:a</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">v</span><span class="o">|</span> <span class="nb">hash</span><span class="p">.</span><span class="nf">delete</span><span class="p">(</span><span class="ss">:a</span><span class="p">);</span> <span class="n">v</span> <span class="o">+</span> <span class="mi">42</span> <span class="p">}</span>
</code></pre>
<p>"store" starts to feel to me like the wrong name, it sounds more like an "update" of an existing key (but Hash#update is an alias of Hash#merge!).</p> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=709102018-03-08T11:05:57ZSoilent (Konstantin x)
<ul></ul><p>mame (Yusuke Endoh) wrote:</p>
<blockquote>
<p>I think it is not so simple to optimize the double lookup by this API. Consider:</p>
<pre><code>hash.store(:a) {|val| 10000.times {|n| hash[n] = true }; val + 42 }
</code></pre>
<p>or:</p>
<pre><code>hash.store(:a) {|val| hash.rehash; val + 42 }
</code></pre>
<p>We need to keep a flag if rehash occurred or not during the block executed.</p>
</blockquote>
<p>I think that an exception should be thrown if the block modifies the hash.</p> Ruby master - Feature #14580: Hash#store accepts a blockhttps://redmine.ruby-lang.org/issues/14580?journal_id=709132018-03-08T11:59:35ZSoilent (Konstantin x)
<ul></ul><p>Eregon (Benoit Daloze) wrote:</p>
<blockquote>
<p>Soilent (Konstantin x) wrote:</p>
<blockquote>
<p>Consider another example <code>hash.store(:time) { |ts| Time.parse(ts) }</code></p>
</blockquote>
<p>That looks weird to me.<br>
Either the Hash is caching String to Time, and then it should use</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Hash</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">h</span><span class="p">,</span><span class="n">k</span><span class="o">|</span> <span class="n">h</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">k</span><span class="p">)</span> <span class="p">}</span>
</code></pre>
<p>or it contains other data and then there seems to be little reason to first store a String for key :time and then only later parse it to a Time instance.</p>
</blockquote>
<p>I see your point, but the example was not about String to Time caching. Let's say you receive an HTTP POST request with the body <code>timestamp=2018-03-08T11:24:44Z&temperature=27</code>. You might want to validate the request and store it in a database:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">begin</span>
<span class="n">params</span><span class="p">.</span><span class="nf">store</span><span class="p">(</span><span class="ss">:temperature</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">tm</span><span class="o">|</span> <span class="no">Integer</span><span class="p">(</span><span class="n">tm</span><span class="p">)</span> <span class="p">}</span>
<span class="n">params</span><span class="p">.</span><span class="nf">store</span><span class="p">(</span><span class="ss">:timestamp</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">ts</span><span class="o">|</span> <span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">ts</span><span class="p">)</span> <span class="p">}</span>
<span class="k">rescue</span> <span class="no">ArgumentError</span> <span class="o">=></span> <span class="n">err</span>
<span class="c1"># Handle invalid request</span>
<span class="k">end</span>
<span class="c1"># Do something with `params`</span>
<span class="n">db</span><span class="p">[</span><span class="ss">:events</span><span class="p">].</span><span class="nf">insert</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>
</code></pre>
<p>I think that Hash#store with a block arg looks quite natural with the rest of the methods from the Hash API.</p>