https://redmine.ruby-lang.org/https://redmine.ruby-lang.org/favicon.ico?17113305112018-06-26T09:39:23ZRuby Issue Tracking SystemRuby master - Feature #14869: Proposal to add Hash#===https://redmine.ruby-lang.org/issues/14869?journal_id=726592018-06-26T09:39:23Znobu (Nobuyoshi Nakada)nobu@ruby-lang.org
<ul></ul><p>osyo (manga osyo) wrote:</p>
<blockquote>
<a name="仕様"></a>
<h2 >仕様<a href="#仕様" class="wiki-anchor">¶</a></h2>
<p>レシーバのキーの要素と引数のハッシュのキーの要素を <code>#===</code> で比較して、全てが真なら <code>true</code> を返し、そうでないなら <code>false</code> を返す。</p>
</blockquote>
<p>キーに対応する値同士を比較するということですね。</p>
<blockquote>
<ul>
<li>
<code>Object#===</code> の場合だと <code>{} === 42</code> が例外ではなくて <code>false</code> を返していたので、<code>Hash#===</code> も <code>false</code> を返すようにした</li>
</ul>
</blockquote>
<p>空でないハッシュをハッシュ以外のオブジェクトと比較しようとするとSEGVします。<br>
<code>to_hash()</code> で変換するか、 <code>Check_Type(hash2, T_HASH)</code> でエラーにするか、空のハッシュ同様 <code>false</code> を返すかしてください。</p> Ruby master - Feature #14869: Proposal to add Hash#===https://redmine.ruby-lang.org/issues/14869?journal_id=726632018-06-26T14:13:49Zosyo (manga osyo)
<ul><li><strong>File</strong> <i>hash_eqq.patch</i> added</li></ul><p>返信ありがとうございます!!</p>
<blockquote>
<blockquote>
<p>レシーバのキーの要素と引数のハッシュのキーの要素を #=== で比較して、全てが真なら true を返し、そうでないなら false を返す。</p>
</blockquote>
</blockquote>
<blockquote>
<p>キーに対応する値同士を比較するということですね。</p>
</blockquote>
<p>はい、その認識で問題ありません。</p>
<blockquote>
<p>空でないハッシュをハッシュ以外のオブジェクトと比較しようとするとSEGVします。<br>
to_hash() で変換するか、 Check_Type(hash2, T_HASH) でエラーにするか、空のハッシュ同様 false を返すかしてください。</p>
</blockquote>
<p>ご指摘ありがとうございます。<br>
<code>{ id: 1 } === 42</code> のように『空でないハッシュをハッシュ以外のオブジェクトと比較』場合は <code>false</code> を返すように修正しました。<br>
また、 <code>{ id: nil } === {}</code> というような比較も <code>true</code> を返していたのでこちらも合わせて <code>false</code> を返すように修正しました。<br>
以下、真・偽になるケースをまとめてみました。</p>
<a name="真のケース"></a>
<h2 >真のケース<a href="#真のケース" class="wiki-anchor">¶</a></h2>
<ul>
<li>レシーバと引数が空のハッシュの場合</li>
<li>レシーバの要素と引数の要素をキーごとに <code>===</code> で比較して全て真の場合</li>
</ul>
<a name="偽のケース"></a>
<h2 >偽のケース<a href="#偽のケース" class="wiki-anchor">¶</a></h2>
<ul>
<li>引数がハッシュ以外の場合</li>
<li>レシーバが空のハッシュで引数が空の Hash でない場合</li>
<li>レシーバの要素と引数の要素をキーごとに <code>===</code> で比較して一つでも偽がある場合</li>
<li>レシーバのキーが引数のハッシュにない場合</li>
</ul> Ruby master - Feature #14869: Proposal to add Hash#===https://redmine.ruby-lang.org/issues/14869?journal_id=727092018-06-29T11:08:10Zznz (Kazuhiro NISHIYAMA)
<ul></ul><p>ほとんどの用途は <code>Hash#<=</code> で足りているようにみえます。<br>
<code>{} <= user</code> は <code>true</code> になるので、<code>{} === user</code> も <code>true</code> の方が <code><=</code> の値の比較を <code>===</code> で行うだけのものということで、わかりやすいのではないかと思いました。</p> Ruby master - Feature #14869: Proposal to add Hash#===https://redmine.ruby-lang.org/issues/14869?journal_id=727372018-06-30T10:47:47Zosyo (manga osyo)
<ul></ul><p>ご意見ありがとうございます。</p>
<blockquote>
<p>ほとんどの用途は <code>Hash#<=</code> で足りているようにみえます。</p>
</blockquote>
<p>機能としては <code>Hash#<=</code> と類似していますが、『<code>Hash#===</code> を定義する事で case-when などで使用することが出来る』というのが主な提案理由となっております。</p>
<blockquote>
<p><code>{} <= user</code> は <code>true</code> になるので、<code>{} === user</code> も <code>true</code> の方が <code><=</code> の値の比較を <code>===</code> で行うだけのものということで、わかりやすいのではないかと思いました。</p>
</blockquote>
<p>確かに『<code>Hash#===</code> は <code>Hash#<=</code> の <code>#===</code> で比較する版』みたいな説明だと理解しやすそうですね。<br>
ただ、 <code>{} === user</code> を <code>true</code> にしてしまうと次のような case-when で互換性が壊れてしまうので、互換性を考えて <code>false</code> を返すのが妥当かと思います。<br>
互換性を壊してまで <code>#<=</code> の挙動に合わせる必要性はなかな、と。</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">check</span> <span class="n">n</span>
<span class="k">case</span> <span class="n">n</span>
<span class="k">when</span> <span class="p">{}</span>
<span class="s2">"空だよ〜"</span>
<span class="k">else</span>
<span class="s2">"空じゃないよ〜"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="nb">p</span> <span class="n">check</span><span class="p">({})</span> <span class="c1"># => "空だよ〜"</span>
<span class="c1"># {} === user を true にしてしまうと結果が変わってしまう…</span>
<span class="nb">p</span> <span class="n">check</span><span class="p">({</span> <span class="ss">name: </span><span class="s2">"mado"</span> <span class="p">})</span> <span class="c1"># => "空じゃないよ〜" と期待する</span>
</code></pre> Ruby master - Feature #14869: Proposal to add Hash#===https://redmine.ruby-lang.org/issues/14869?journal_id=727642018-07-02T08:42:07Znobu (Nobuyoshi Nakada)nobu@ruby-lang.org
<ul></ul><p>空の場合は <code>Enumerable#all?</code> と類似の話なので、 <code>true</code> のほうがいいんじゃないでしょうか。<br>
また、再帰的なハッシュに対して使用したときに無限再帰にならないようにする必要がありそうです。</p> Ruby master - Feature #14869: Proposal to add Hash#===https://redmine.ruby-lang.org/issues/14869?journal_id=727882018-07-03T11:22:12Zosyo (manga osyo)
<ul></ul><blockquote>
<p>空の場合は Enumerable#all? と類似の話なので、 true のほうがいいんじゃないでしょうか。</p>
</blockquote>
<p>なるほど、参考になります。</p>
<blockquote>
<p>また、再帰的なハッシュに対して使用したときに無限再帰にならないようにする必要がありそうです。</p>
</blockquote>
<p>あーこれは確かに問題になりそうですね。対策を考えてみたいと思います。<br>
ありがとうございます。</p> Ruby master - Feature #14869: Proposal to add Hash#===https://redmine.ruby-lang.org/issues/14869?journal_id=729612018-07-16T04:35:23Zosyo (manga osyo)
<ul><li><strong>File</strong> <a href="/attachments/7240">hash_eqq.patch</a> <a class="icon-only icon-download" title="Download" href="/attachments/download/7240/hash_eqq.patch">hash_eqq.patch</a> added</li><li><strong>File</strong> deleted (<del><i>hash_eqq.patch</i></del>)</li><li><strong>File</strong> deleted (<del><i>hash_eqq.patch</i></del>)</li></ul><blockquote>
<p>また、再帰的なハッシュに対して使用したときに無限再帰にならないようにする必要がありそうです。</p>
</blockquote>
<p><code>Hash#==</code> を参考に <code>rb_exec_recursive_paired</code> を使用して再帰チェックするようにしてみました。<br>
<code>rb_exec_recursive_paired</code> の挙動に関して詳細に理解していないのでそのあたりをレビューして頂けると助かります。</p> Ruby master - Feature #14869: Proposal to add Hash#===https://redmine.ruby-lang.org/issues/14869?journal_id=730072018-07-19T00:12:56Zmrkn (Kenta Murata)muraken@gmail.com
<ul><li><strong>Related to</strong> <i><a class="issue tracker-2 status-1 priority-4 priority-default" href="/issues/14916">Feature #14916</a>: Proposal to add Array#===</i> added</li></ul> Ruby master - Feature #14869: Proposal to add Hash#===https://redmine.ruby-lang.org/issues/14869?journal_id=733132018-08-04T15:55:36Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<ul></ul><p>I would agree with <code>===</code> being more useful than <code><=</code>, as <code>case</code>, <code>all?</code>, <code>grep</code>, and other methods use it implicitly.</p>
<p>This would be an amazing addition for Ruby, and would bring us closer to pattern matching syntax.</p>
<p>The great part about this is your implementation uses <code>===</code> to compare values as well. This makes it very flexible, and extremely useful.</p>
<p><strong>Aside / Offtopic</strong></p>
<p>This may be unrelated, and if so feel free to tell me to open another issue, but what if it worked on Objects:</p>
<pre><code>Person = Struct.new(:id, :name, :age)
people = [{:id=>1, :name=>"Homu", :age=>13}, {:id=>2, :name=>"mami", :age=>14}].map { |p| Person.new(*p.values) }
people.grep(age: 10..13) # => [#<struct Person id=1, name="Homu", age=13>]
</code></pre> Ruby master - Feature #14869: Proposal to add Hash#===https://redmine.ruby-lang.org/issues/14869?journal_id=733292018-08-05T22:53:34Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<ul></ul><p>I recently got permission to repurpose the <code>Any</code> gem, which gives us this:</p>
<pre><code>require 'any'
case {id: 1, name: 'foo', age: 42}
when {id: Any, name: /^f/, age: Any} then true
else false
end
# => true
case {id: 1, name: 'foo'}
when {id: Any, name: /^f/, age: Any} then true
else false
end
# => false
</code></pre>
<p>That should make this even more flexible.</p>
<p><a href="https://github.com/baweaver/any" class="external">https://github.com/baweaver/any</a></p> Ruby master - Feature #14869: Proposal to add Hash#===https://redmine.ruby-lang.org/issues/14869?journal_id=733862018-08-08T20:51:39Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<ul></ul><p>I had mentioned this in the <code>Array#===</code> topic for consideration: <a href="https://bugs.ruby-lang.org/issues/14916#note-6" class="external">https://bugs.ruby-lang.org/issues/14916#note-6</a></p>
<p>In the comparison, we are returning false if the other value is not a Hash:</p>
<p>(line 4011)</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="no">RB_TYPE_P</span><span class="p">(</span><span class="n">hash2</span><span class="p">,</span> <span class="no">T_HASH</span><span class="p">))</span> <span class="k">return</span> <span class="no">Qfalse</span><span class="p">;</span>
</code></pre>
<p>Much like the suggestion in the Array topic, would it be a good idea to leverage <code>to_hash</code>? In Ruby this means that an object behaves like a hash, allowing us duck-typing and more flexibility.</p>
<p>Consider an object type that responds to <code>to_hash</code> with its instance properties, we could use the same method of querying with a minimal speed penalty for coercion.</p>
<p>We could first check if something responds to <code>to_hash</code> if it is not already a Hash. If it does, we can coerce and operate on that value, if not we've not incurred much of a speed penalty because <code>Hash === Hash</code> would never go into that conditional.</p> Ruby master - Feature #14869: Proposal to add Hash#===https://redmine.ruby-lang.org/issues/14869?journal_id=733882018-08-09T02:09:02Zosyo (manga osyo)
<ul><li><strong>File</strong> <a href="/attachments/7313">hash_eqq.patch</a> <a class="icon-only icon-download" title="Download" href="/attachments/download/7313/hash_eqq.patch">hash_eqq.patch</a> added</li></ul><blockquote>
<p>Much like the suggestion in the Array topic, would it be a good idea to leverage to_hash? In Ruby this means that an object behaves like a hash, allowing us duck-typing and more flexibility.</p>
</blockquote>
<p>Support call <code>to_hash</code>.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">o</span> <span class="o">=</span> <span class="no">Object</span><span class="p">.</span><span class="nf">new</span>
<span class="k">def</span> <span class="nc">o</span><span class="o">.</span><span class="nf">to_hash</span>
<span class="p">{</span> <span class="ss">name: </span><span class="s2">"homu"</span><span class="p">,</span> <span class="ss">age: </span><span class="mi">14</span> <span class="p">}</span>
<span class="k">end</span>
<span class="p">{</span> <span class="ss">name: </span><span class="sr">/^h/</span> <span class="p">}</span> <span class="o">===</span> <span class="n">o</span>
<span class="c1"># => true</span>
</code></pre> Ruby master - Feature #14869: Proposal to add Hash#===https://redmine.ruby-lang.org/issues/14869?journal_id=733912018-08-09T04:03:53Ztimriley (Tim Riley)
<ul></ul><p>This is looking like a really positive improvement, thank you!</p>
<p>Would you consider taking this one step further and supporting the explicit converter, <code>#to_h</code> instead of (or as well as, if required) the implicit <code>#to_hash</code> converter? This would allow using <code>Hash#===</code> to match against e.g. <code>Struct</code> instances (which have <code>#to_h</code> but not <code>#to_hash</code>) plus any other kind of object that doesn't want to pretend to "be" a hash, but rather provide the interface for converting to one.</p>
<p>There are penalties for implementing <code>#to_hash</code>, like implicit destructuring when an object is passed to a method with kwrest params, so its fair to expect that not every class would want to do it. <code>#to_h</code>, on the other hand, is much more common (just like we see in the example of Ruby's own <code>Struct</code>), so supporting that would make this matcher even more flexible, usable, and powerful.</p>