https://bugs.ruby-lang.org/https://bugs.ruby-lang.org/favicon.ico?17113305112018-04-24T16:19:59ZRuby Issue Tracking SystemRuby master - Feature #14709: Proper pattern matchinghttps://bugs.ruby-lang.org/issues/14709?journal_id=716262018-04-24T16:19:59Zshevegen (Robert A. Heiler)shevegen@gmail.com
<ul></ul><p>I think only matz can answer that.</p>
<p>On a side note, I am not aware of a issue request about it. Has this<br>
already been suggested, or is it only a presentation? If it has not<br>
yet been proposed formally through an issue request on the tracker<br>
here, then perhaps that should be done.</p> Ruby master - Feature #14709: Proper pattern matchinghttps://bugs.ruby-lang.org/issues/14709?journal_id=716292018-04-25T00:39:21Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<ul></ul><p>I am the author of aforementioned Qo, so I believe I can shed some light on a few things:</p>
<ul>
<li>The thinking behind Qo</li>
<li>Reusable syntax features</li>
<li>Making it feel like Ruby</li>
<li>Shortcomings of current syntax</li>
<li>Performance considerations</li>
<li>Ideas moving forward</li>
</ul>
<p>I will warn you that this may be a very long post, so I will attempt to break it into the above sections to make it easier to read. The last section will propose my ideas on syntax, and will effectively summarize the others.</p>
<p>You can find Qo here: <a href="https://github.com/baweaver/qo" class="external">https://github.com/baweaver/qo</a></p>
<p>I have tried to be detailed in my documentation, and will continue to expand upon it.</p>
<a name="The-thinking-behind-Qo"></a>
<h3 >The thinking behind Qo<a href="#The-thinking-behind-Qo" class="wiki-anchor">¶</a></h3>
<p>Qo emerged as a result of the TC39 proposal for pattern matching as well as a conversation I had at Fog City Ruby about real pattern matching in Ruby, and made me ask a few questions:</p>
<ul>
<li>How can we do this using what Ruby already has?</li>
<li>How can we make this look and feel like Ruby?</li>
<li>How can we make the performance acceptable?</li>
</ul>
<p>I wrote an article that explains, in more detail than I will here, the first point: <a href="https://medium.com/@baweaver/for-want-of-pattern-matching-in-ruby-the-creation-of-qo-c3b267109b25" class="external">https://medium.com/@baweaver/for-want-of-pattern-matching-in-ruby-the-creation-of-qo-c3b267109b25</a></p>
<a name="How-can-we-use-current-Ruby-syntax-and-make-it-feel-like-Ruby"></a>
<h3 >How can we use current Ruby syntax and make it feel like Ruby?<a href="#How-can-we-use-current-Ruby-syntax-and-make-it-feel-like-Ruby" class="wiki-anchor">¶</a></h3>
<p>Ruby is an exceptionally powerful language, and jumping back to the basics we can find <code>===</code> and <code>to_proc</code>. Both are used throughout current language features with implicit syntax:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># === is implied around case statements</span>
<span class="k">case</span> <span class="s1">'string'</span>
<span class="k">when</span> <span class="no">String</span> <span class="k">then</span> <span class="mi">1</span>
<span class="k">else</span> <span class="mi">2</span>
<span class="k">end</span>
<span class="c1"># in grep</span>
<span class="n">data</span><span class="p">.</span><span class="nf">grep</span><span class="p">(</span><span class="no">String</span><span class="p">)</span>
<span class="c1"># and now in predicate methods</span>
<span class="n">data</span><span class="p">.</span><span class="nf">any?</span><span class="p">(</span><span class="no">String</span><span class="p">)</span>
</code></pre>
<p>For <code>to_proc</code> it's a very similar story:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">data</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="no">ProcObject</span><span class="p">)</span>
</code></pre>
<p>Both are well understood in Ruby and utilize common features. Implementing classes that expose such methods allow them to utilize these same features for more powerful APIs.</p>
<p><code>to_ary</code> was one of my next potentials for destructuring and right hand assignment emulation in pattern matching, but I have not yet found an acceptable solution to this.</p>
<p>Point being, by using existing language features we retain a very Ruby-like feel:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">case</span> <span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s1">''</span><span class="p">,</span> <span class="kp">nil</span><span class="p">)</span>
<span class="k">when</span> <span class="no">Qo</span><span class="p">[</span><span class="ss">age: :nil?</span><span class="p">]</span> <span class="k">then</span> <span class="s1">'No age provided!'</span>
<span class="k">else</span> <span class="s1">'nope'</span>
<span class="k">end</span>
</code></pre>
<p>Though this brings us to the next point: limitations of current syntax.</p>
<a name="Limitations-of-current-syntax"></a>
<h3 >Limitations of current syntax<a href="#Limitations-of-current-syntax" class="wiki-anchor">¶</a></h3>
<p>One of the key items of pattern matching that I would very much enjoy is Right Hand Assignment, or the ability to inject local variables in the resulting branch of a match.</p>
<p>Case statements will not allow this, so something such as this is not possible as <code>when</code> will not be able to return a value:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">people</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">person</span><span class="o">|</span>
<span class="k">case</span> <span class="n">person</span>
<span class="k">when</span> <span class="no">Qo</span><span class="p">.</span><span class="nf">m</span><span class="p">(</span><span class="ss">name: </span><span class="p">:</span><span class="o">*</span><span class="p">,</span> <span class="ss">age: </span><span class="mi">20</span><span class="o">..</span><span class="mi">99</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">person</span><span class="o">|</span> <span class="s2">"</span><span class="si">#{</span><span class="n">person</span><span class="p">.</span><span class="nf">name</span><span class="si">}</span><span class="s2"> is an adult that is </span><span class="si">#{</span><span class="n">person</span><span class="p">.</span><span class="nf">age</span><span class="si">}</span><span class="s2"> years old"</span> <span class="p">}</span>
<span class="k">else</span> <span class="no">Qo</span><span class="p">.</span><span class="nf">m</span><span class="p">(:</span><span class="o">*</span><span class="p">)</span>
<span class="k">end</span>
<span class="p">}</span>
</code></pre>
<p>This was the reason behind me introducing the concept of <code>match</code> with Qo:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Qo</span><span class="p">.</span><span class="nf">match</span><span class="p">([</span><span class="s1">'Robert'</span><span class="p">,</span> <span class="mi">22</span><span class="p">],</span>
<span class="no">Qo</span><span class="p">.</span><span class="nf">m</span><span class="p">(:</span><span class="o">*</span><span class="p">,</span> <span class="mi">20</span><span class="o">..</span><span class="mi">99</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">n</span><span class="p">,</span> <span class="n">a</span><span class="o">|</span> <span class="s2">"</span><span class="si">#{</span><span class="n">n</span><span class="si">}</span><span class="s2"> is an adult that is </span><span class="si">#{</span><span class="n">a</span><span class="si">}</span><span class="s2"> years old"</span> <span class="p">},</span>
<span class="no">Qo</span><span class="p">.</span><span class="nf">m</span><span class="p">(:</span><span class="o">*</span><span class="p">)</span>
<span class="p">)</span>
<span class="c1"># => "Robert is an adult that is 22 years old"</span>
<span class="n">people</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="no">Qo</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span>
<span class="no">Qo</span><span class="p">.</span><span class="nf">m</span><span class="p">(:</span><span class="o">*</span><span class="p">,</span> <span class="mi">20</span><span class="o">..</span><span class="mi">99</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">n</span><span class="p">,</span> <span class="n">a</span><span class="o">|</span> <span class="s2">"</span><span class="si">#{</span><span class="n">n</span><span class="si">}</span><span class="s2"> is an adult that is </span><span class="si">#{</span><span class="n">a</span><span class="si">}</span><span class="s2"> years old"</span> <span class="p">},</span>
<span class="no">Qo</span><span class="p">.</span><span class="nf">m</span><span class="p">(:</span><span class="o">*</span><span class="p">)</span>
<span class="p">))</span>
<span class="c1"># => "Robert is an adult that is 22 years old"</span>
</code></pre>
<p>It is not as elegant and Ruby-like as I would like, but it is more succinct than some of the other implementations based in more functional language paradigms using purely lambda composition.</p>
<p>As a result, it also ends up being faster which leads me to the next section:</p>
<a name="Performance-Considerations"></a>
<h3 >Performance Considerations<a href="#Performance-Considerations" class="wiki-anchor">¶</a></h3>
<p>I try and be very honest about the performance concerns related to Qo, being that it is heavily dynamic in nature. Admittedly in ways it tries to be too clever, which I am attempting to reconcile for greater performance gains.</p>
<p>The performance results can be seen here: <a href="https://github.com/baweaver/qo/blob/master/performance_report.txt" class="external">https://github.com/baweaver/qo/blob/master/performance_report.txt</a></p>
<p>All tests are specified in the Rakefile and compare Qo to a Vanilla variant of the same task. The performance was better than I had anticipated but still did not reach quite the speed that I had wanted it to.<br>
I will continue to test against this and make improvements. It is likely that I can cut down this time substantially from where it currently is.</p>
<p>It is also the reason you may see certain <code>===</code> related tickets on speed improvement :)</p>
<p>I will endeavor to bring more comprehensive performance testing, especially around pattern matching as I have no results to this point.</p>
<a name="Ideas-moving-forward"></a>
<h3 >Ideas moving forward<a href="#Ideas-moving-forward" class="wiki-anchor">¶</a></h3>
<p>I would be interested in a match syntax such as this:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">new_value</span> <span class="o">=</span> <span class="n">match</span> <span class="n">value</span>
<span class="k">when</span> <span class="o">%</span><span class="n">m</span><span class="p">(</span><span class="ss">:_</span><span class="p">,</span> <span class="mi">20</span><span class="o">..</span><span class="mi">99</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">_</span><span class="p">,</span> <span class="n">age</span><span class="o">|</span> <span class="n">age</span> <span class="o">+</span> <span class="mi">1</span> <span class="p">}</span>
<span class="k">else</span> <span class="p">{</span> <span class="o">|</span><span class="n">object</span><span class="o">|</span> <span class="o">...</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre>
<p>The block syntax will make this a less magical option as it can explicitly capture details on what was matched. Keyword arguments could be used as well, but few know of the keyword arguments in blocks:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">json</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="o">|</span><span class="ss">name: </span><span class="s1">'default_name'</span><span class="p">,</span> <span class="ss">age: </span><span class="mi">42</span><span class="o">|</span> <span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">age</span><span class="p">)</span> <span class="p">}</span>
</code></pre>
<p>Such syntax exists most similarly in Scala, notably with Case Classes. Other similar implementations are present in the JS TC39 proposal and to a lesser extent in Haskell.</p>
<p>I have yet to think of a way to implement Left Hand Assignment like Javascript destructuring, and I do not have ideas on how to achieve such a thing beyond using <code>to_ary</code> for implicit destructuring.</p>
<a name="Conclusion"></a>
<h3 >Conclusion<a href="#Conclusion" class="wiki-anchor">¶</a></h3>
<p>Thank you for your time in reading, and I apologize for my verbosity, but this is a feature I would very much like to see in Ruby Core some day.</p>
<p>I would be more than happy to provide alternative ideas on syntax in regards to this idea if you do not find the proposed ones satisfactory.</p>
<ul>
<li>baweaver</li>
</ul> Ruby master - Feature #14709: Proper pattern matchinghttps://bugs.ruby-lang.org/issues/14709?journal_id=716302018-04-25T01:35:35Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<ul></ul><p>It was mentioned to me on Reddit that I should detail exactly what cases of pattern matching would entail.</p>
<p>For the case of Qo, these include four primary categories:</p>
<ul>
<li>Array matches Array</li>
<li>Array matches Object</li>
<li>Hash matches Hash</li>
<li>Hash matches Object</li>
</ul>
<p>From there each category typically descends into the following chain:</p>
<ol>
<li>Wildcards: <code>b == WILDCARD</code>
</li>
<li>Strict equality: <code>a == b</code>
</li>
<li>Case equality: <code>a === b</code>
</li>
<li>Predicates: <code>a.public_send(b)</code>
</li>
</ol>
<p>These vary between Array and Hash style matches.</p>
<p>This will also be long as it explains the exact contract I've implemented, so as to be a possible inspiration for design level concerns of such a feature.</p>
<a name="Array-matches-Array"></a>
<h3 >Array matches Array<a href="#Array-matches-Array" class="wiki-anchor">¶</a></h3>
<p>An Array matched against another Array in Qo will result in them being compared positionally on intersection.</p>
<p>In the most basic of cases, <code>[1,1]</code> matched against itself would compare the elements by their index, resulting in (using a hypothetical match method):</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">match</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">])</span> <span class="c1"># => true</span>
<span class="c1"># [1, 1]</span>
<span class="c1"># [1, 1]</span>
</code></pre>
<p>My decision with Qo was to be left-side permissive on length, such that this will also return true:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">match</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">])</span> <span class="c1">#=> true</span>
<span class="c1"># [1, 1]</span>
<span class="c1"># [1, 1, 2, 3, 4]</span>
</code></pre>
<p>Only the first two items would be compared.</p>
<p>For this, comparisons will be run as:</p>
<ol>
<li>Wildcard matches: <code>b == :*</code>
</li>
<li>Strict equality: <code>[1, 1] == [1, 1]</code>
</li>
<li>Case equality: <code>match([Integer, 1..10], [1, 1])</code>
</li>
<li>Predicate: <code>match([:odd?, :zero?], [1, 0])</code>
</li>
</ol>
<p>I would say that for performance concerns case 4 could be subsumed into case 3 by transferring the symbols to Procs instead of relying on <code>public_send</code>.</p>
<a name="Array-matches-Object"></a>
<h3 >Array matches Object<a href="#Array-matches-Object" class="wiki-anchor">¶</a></h3>
<p>An Array matched against an Object will attempt to compare that Object to everything in the Array via:</p>
<ol>
<li>Wildcard matches: <code>b == :*</code>
</li>
<li>Case equality: <code>match([Integer, 1..10], 1)</code>
</li>
<li>Predicate: <code>match([:odd?, :zero?], 1)</code>
</li>
</ol>
<p>In some cases this can be emulated with <code>compose</code> as suggested in another bug with procs, but <code>===</code> responding values will no longer have that flexibility.</p>
<p>Even if an <code>Array#to_proc</code> were to exist, composing against <code>===</code> responding values would fail.</p>
<p>This would be more difficult to optimize for performance</p>
<a name="Hash-matches-Hash"></a>
<h3 >Hash matches Hash<a href="#Hash-matches-Hash" class="wiki-anchor">¶</a></h3>
<p>A Hash matched against another Hash would be via intersection of keys, and in the following order:</p>
<ol>
<li>Key exists: <code>a.key?(b)</code>
</li>
<li>Wildcard matches: <code>b == :*</code>
</li>
<li>Recurse if value is a Hash</li>
<li>Case equality: <code>b[k] === a[k]</code>
</li>
<li>Predicate: <code>a[k].public_send(b)</code>
</li>
<li>Repeat with String coercion of key</li>
</ol>
<p>Qo tries to be clever here, and starts to be more strict. It wants the key to exist or it counts a non-match even if a wildcard was provided to it.</p>
<p>The cleverness comes into play in that it tries the string version of the key as well, just in case. This is a performance bottleneck, but also a nice to have considering how often Rubyists get the two mixed up. Problem is it adds a good deal of overhead, and keyword arguments cannot take string keys with hashrockets.</p>
<a name="Hash-matches-Object"></a>
<h3 >Hash matches Object<a href="#Hash-matches-Object" class="wiki-anchor">¶</a></h3>
<p>This starts to look a lot more like ActiveRecord syntax, except in that it applies to plain ruby objects using <code>public_send</code>. It applies in the following order:</p>
<ol>
<li>Object responds to key</li>
<li>Wildcard matches</li>
<li>Case equality</li>
<li>Predicate</li>
</ol>
<p>It is also strict about the presence of a key / method before continuing, even if it were to be a wildcard.</p>
<p>Essentially each key is a method to send to the object, and each value is what it must match against somehow:</p>
<pre><code>match(Person('Foo', 42), name: :*, age: 40..50)
</code></pre>
<a name="On-Docs"></a>
<h3 >On Docs<a href="#On-Docs" class="wiki-anchor">¶</a></h3>
<p>A majority of this is explained in more detail in the Qo docs, as well as practical examples I've tried myself in the wild.</p>
<p>If you have any other questions or concerns I would be happy to address them.</p> Ruby master - Feature #14709: Proper pattern matchinghttps://bugs.ruby-lang.org/issues/14709?journal_id=716332018-04-25T02:53:43Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<ul></ul><p>It should also be mentioned that the way I achieved the pattern matching mentioned above was by using a status container in the style of Elixir (<code>{:ok, result}</code> except <code>[true, result]</code>):</p>
<p><a href="https://github.com/baweaver/qo/blob/83577f80a47015e60d833da62a1220a08c00482d/lib/qo/matchers/pattern_match.rb#L77-L91" class="external">https://github.com/baweaver/qo/blob/83577f80a47015e60d833da62a1220a08c00482d/lib/qo/matchers/pattern_match.rb#L77-L91</a></p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># Immediately invokes a PatternMatch</span>
<span class="c1">#</span>
<span class="c1"># @param target [Any]</span>
<span class="c1"># Target to run against and pipe to the associated block if it</span>
<span class="c1"># "matches" any of the GuardBlocks</span>
<span class="c1">#</span>
<span class="c1"># @return [Any | nil] Result of the piped block, or nil on a miss</span>
<span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="n">target</span><span class="p">)</span>
<span class="vi">@matchers</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">guard_block_matcher</span><span class="o">|</span>
<span class="n">did_match</span><span class="p">,</span> <span class="n">match_result</span> <span class="o">=</span> <span class="n">guard_block_matcher</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="n">target</span><span class="p">)</span>
<span class="k">return</span> <span class="n">match_result</span> <span class="k">if</span> <span class="n">did_match</span>
<span class="p">}</span>
<span class="kp">nil</span>
<span class="k">end</span>
</code></pre>
<p>By inheriting from a matcher and overriding its <code>to_proc</code> method, one can use <code>super</code> to get the boolean result of the matcher. If that result is true, we can invoke the block function passed to the matcher to simulate Right Hand Assignment:</p>
<p><a href="https://github.com/baweaver/qo/blob/83577f80a47015e60d833da62a1220a08c00482d/lib/qo/matchers/guard_block_matcher.rb#L38-L48" class="external">https://github.com/baweaver/qo/blob/83577f80a47015e60d833da62a1220a08c00482d/lib/qo/matchers/guard_block_matcher.rb#L38-L48</a></p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># Overrides the base matcher's #to_proc to wrap the value in a status</span>
<span class="c1"># and potentially call through to the associated block if a base</span>
<span class="c1"># matcher would have passed</span>
<span class="c1">#</span>
<span class="c1"># @return [Proc[Any] - (Bool, Any)]</span>
<span class="c1"># (status, result) tuple</span>
<span class="k">def</span> <span class="nf">to_proc</span>
<span class="no">Proc</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">target</span><span class="o">|</span>
<span class="k">super</span><span class="p">[</span><span class="n">target</span><span class="p">]</span> <span class="p">?</span> <span class="p">[</span><span class="kp">true</span><span class="p">,</span> <span class="vi">@fn</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="n">target</span><span class="p">)]</span> <span class="p">:</span> <span class="no">NON_MATCH</span>
<span class="p">}</span>
<span class="k">end</span>
</code></pre>
<p>This is done to prevent false negatives from legitimate falsey values that might be returned as a result of a match, and will be a similar concern in implementation.</p> Ruby master - Feature #14709: Proper pattern matchinghttps://bugs.ruby-lang.org/issues/14709?journal_id=716342018-04-25T05:22:13Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<ul></ul><a name="On-Pattern-Matching-in-Other-Languages"></a>
<h2 >On Pattern Matching in Other Languages<a href="#On-Pattern-Matching-in-Other-Languages" class="wiki-anchor">¶</a></h2>
<p>For continuity, we should also go over implementations in other languages.</p>
<p>Note that I am not an expert in any of these languages, and only proficient in Javascript. I would encourage others to source examples and correct any shortcomings of my answers.</p>
<a name="Javascript"></a>
<h3 >Javascript<a href="#Javascript" class="wiki-anchor">¶</a></h3>
<p>I think the most practical one to read right now is the current TC39 proposal for pattern matching in Javascript:</p>
<p><a href="https://github.com/tc39/proposal-pattern-matching" class="external">https://github.com/tc39/proposal-pattern-matching</a></p>
<p>The pull request and discussion around it is particularly enlightening as to the challenges and concerns present in creating such a feature, and as it is a stage 0 proposal it gives a very new view to the idea as well.</p>
<p>An example:</p>
<pre><code class="javascript syntaxhl" data-language="javascript"><span class="kd">const</span> <span class="nx">getLength</span> <span class="o">=</span> <span class="nx">vector</span> <span class="o">=></span> <span class="nf">match </span><span class="p">(</span><span class="nx">vector</span><span class="p">)</span> <span class="p">{</span>
<span class="p">{</span> <span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">z</span> <span class="p">}</span> <span class="o">=></span> <span class="nb">Math</span><span class="p">.</span><span class="nf">sqrt</span><span class="p">(</span><span class="nx">x</span> <span class="o">**</span> <span class="mi">2</span> <span class="o">+</span> <span class="nx">y</span> <span class="o">**</span> <span class="mi">2</span> <span class="o">+</span> <span class="nx">z</span> <span class="o">**</span> <span class="mi">2</span><span class="p">),</span>
<span class="p">{</span> <span class="nx">x</span><span class="p">,</span> <span class="nx">y</span> <span class="p">}</span> <span class="o">=></span> <span class="nb">Math</span><span class="p">.</span><span class="nf">sqrt</span><span class="p">(</span><span class="nx">x</span> <span class="o">**</span> <span class="mi">2</span> <span class="o">+</span> <span class="nx">y</span> <span class="o">**</span> <span class="mi">2</span><span class="p">),</span>
<span class="p">[...</span><span class="nx">etc</span><span class="p">]</span> <span class="o">=></span> <span class="nx">vector</span><span class="p">.</span><span class="nx">length</span>
<span class="p">}</span>
<span class="nf">getLength</span><span class="p">({</span><span class="na">x</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="na">y</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="na">z</span><span class="p">:</span> <span class="mi">3</span><span class="p">})</span> <span class="c1">// 3.74165</span>
</code></pre>
<p>It would be disingenuous to not mention that Javascript also has a concept of destructuring:</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment" class="external">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment</a></p>
<p>This gives it a significant advantage in defining such a pattern, and would be difficult to implement in Ruby potentially with the Hash / Block distinctions. It's possible, yes, but something to keep in mind.</p>
<p>Noted that I'd love destructuring as well, but that may be overkill. I wonder if it may be a prerequisite in some ways though, as Left Hand Assignment is huge to pattern matching.</p>
<a name="Elixir"></a>
<h3 >Elixir<a href="#Elixir" class="wiki-anchor">¶</a></h3>
<p>A language directly inspired by Ruby, perhaps we can borrow back. Elixir has several types of pattern matching.</p>
<p><a href="https://elixir-lang.org/getting-started/pattern-matching.html" class="external">https://elixir-lang.org/getting-started/pattern-matching.html</a></p>
<p>The first should look very similar to the Javascript example, as destructuring is basically a form of pattern matching:</p>
<pre><code class="elixir syntaxhl" data-language="elixir"><span class="n">iex</span><span class="o">></span> <span class="p">{</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">}</span> <span class="o">=</span> <span class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="s2">"world"</span><span class="p">,</span> <span class="mi">42</span><span class="p">}</span>
<span class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="s2">"world"</span><span class="p">,</span> <span class="mi">42</span><span class="p">}</span>
</code></pre>
<p>Notedly if the sides are not congruent a match will fail. This does introduce the idea that <code>=</code> is not really assignment, but a pattern match itself. I don't see that as an easy or really even practical thing to be able to do in Ruby.</p>
<p>The next is quite similar to overloading methods in Java:</p>
<p><a href="https://blog.carbonfive.com/2017/10/19/pattern-matching-in-elixir-five-things-to-remember/" class="external">https://blog.carbonfive.com/2017/10/19/pattern-matching-in-elixir-five-things-to-remember/</a></p>
<pre><code class="elixir syntaxhl" data-language="elixir"><span class="c1"># greeter.exs</span>
<span class="k">defmodule</span> <span class="no">Greeter</span> <span class="k">do</span>
<span class="k">def</span> <span class="n">hello</span><span class="p">(</span><span class="ss">:jane</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="s2">"Hello Jane"</span>
<span class="k">def</span> <span class="n">hello</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="k">do</span>
<span class="s2">"Hello </span><span class="si">#{</span><span class="n">name</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># calculator.exs</span>
<span class="k">defmodule</span> <span class="no">Calculator</span> <span class="k">do</span>
<span class="k">def</span> <span class="n">multiply_by_two</span><span class="p">([]),</span> <span class="k">do</span><span class="p">:</span> <span class="n">return</span> <span class="p">[]</span>
<span class="k">def</span> <span class="n">multiply_by_two</span><span class="p">([</span><span class="n">head</span> <span class="o">|</span> <span class="n">tail</span><span class="p">])</span> <span class="k">do</span>
<span class="p">[</span><span class="n">head</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">|</span> <span class="n">multiply_by_two</span><span class="p">(</span><span class="n">tail</span><span class="p">)]</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>A similar idea exists in Haskell and other languages, but we'll get to them later. The concern here is that this type of technique is already done heavily in Ruby using Case as a type dispatcher:</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="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="k">case</span> <span class="n">args</span><span class="p">.</span><span class="nf">first</span>
<span class="k">when</span> <span class="no">String</span> <span class="k">then</span> <span class="n">string_variant</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
<span class="k">when</span> <span class="no">Integer</span> <span class="k">then</span> <span class="n">integer_variant</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
<span class="o">...</span>
<span class="k">end</span>
</code></pre>
<p>Point being that pattern matching may exacerbate that issue that Elixir solves. That's also a very hard feature to account for.</p>
<a name="Haskell"></a>
<h3 >Haskell<a href="#Haskell" class="wiki-anchor">¶</a></h3>
<p>Haskell has several different styles of pattern matching, some of which very similar to the Elixir example right above:</p>
<p><a href="http://learnyouahaskell.com/syntax-in-functions" class="external">http://learnyouahaskell.com/syntax-in-functions</a></p>
<pre><code class="haskell syntaxhl" data-language="haskell"><span class="n">factorial</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Integral</span> <span class="n">a</span><span class="p">)</span> <span class="o">=></span> <span class="n">a</span> <span class="o">-></span> <span class="n">a</span>
<span class="n">factorial</span> <span class="mi">0</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">factorial</span> <span class="n">n</span> <span class="o">=</span> <span class="n">n</span> <span class="o">*</span> <span class="n">factorial</span> <span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
</code></pre>
<p>The guard statement may be more in line with what we would recognize as a case statement:</p>
<pre><code class="haskell syntaxhl" data-language="haskell"><span class="n">max'</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Ord</span> <span class="n">a</span><span class="p">)</span> <span class="o">=></span> <span class="n">a</span> <span class="o">-></span> <span class="n">a</span> <span class="o">-></span> <span class="n">a</span>
<span class="n">max'</span> <span class="n">a</span> <span class="n">b</span>
<span class="o">|</span> <span class="n">a</span> <span class="o">></span> <span class="n">b</span> <span class="o">=</span> <span class="n">a</span>
<span class="o">|</span> <span class="n">otherwise</span> <span class="o">=</span> <span class="n">b</span>
</code></pre>
<p>This also treats <code>=</code> as a pattern match, and relies on some of Haskell's laziness. Perhaps case expressions are the closest parallel though:</p>
<pre><code class="haskell syntaxhl" data-language="haskell"><span class="n">describeList</span> <span class="o">::</span> <span class="p">[</span><span class="n">a</span><span class="p">]</span> <span class="o">-></span> <span class="kt">String</span>
<span class="n">describeList</span> <span class="n">xs</span> <span class="o">=</span> <span class="s">"The list is "</span> <span class="o">++</span> <span class="kr">case</span> <span class="n">xs</span> <span class="kr">of</span> <span class="kt">[]</span> <span class="o">-></span> <span class="s">"empty."</span>
<span class="p">[</span><span class="n">x</span><span class="p">]</span> <span class="o">-></span> <span class="s">"a singleton list."</span>
<span class="n">xs</span> <span class="o">-></span> <span class="s">"a longer list."</span>
</code></pre>
<a name="Scala"></a>
<h3 >Scala<a href="#Scala" class="wiki-anchor">¶</a></h3>
<p>Scala is likely one of the closest to Ruby in terms of syntax in pattern matching:</p>
<p><a href="https://docs.scala-lang.org/tour/pattern-matching.html" class="external">https://docs.scala-lang.org/tour/pattern-matching.html</a></p>
<pre><code class="scala syntaxhl" data-language="scala"><span class="k">def</span> <span class="nf">matchTest</span><span class="o">(</span><span class="n">x</span><span class="k">:</span> <span class="kt">Int</span><span class="o">)</span><span class="k">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">x</span> <span class="k">match</span> <span class="o">{</span>
<span class="k">case</span> <span class="mi">1</span> <span class="k">=></span> <span class="s">"one"</span>
<span class="k">case</span> <span class="mi">2</span> <span class="k">=></span> <span class="s">"two"</span>
<span class="k">case</span> <span class="k">_</span> <span class="k">=></span> <span class="s">"many"</span>
<span class="o">}</span>
<span class="nf">matchTest</span><span class="o">(</span><span class="mi">3</span><span class="o">)</span> <span class="c1">// many</span>
<span class="nf">matchTest</span><span class="o">(</span><span class="mi">1</span><span class="o">)</span> <span class="c1">// one</span>
</code></pre>
<p>Case classes especially are interesting in that some Ruby variants may be able to copy such techniques with constructor contracts and <code>self.to_proc</code>:</p>
<pre><code class="scala syntaxhl" data-language="scala"><span class="k">abstract</span> <span class="k">class</span> <span class="nc">Notification</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">Email</span><span class="o">(</span><span class="n">sender</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">title</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">body</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">Notification</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">SMS</span><span class="o">(</span><span class="n">caller</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">message</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">Notification</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">VoiceRecording</span><span class="o">(</span><span class="n">contactName</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">link</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">Notification</span>
<span class="k">def</span> <span class="nf">showNotification</span><span class="o">(</span><span class="n">notification</span><span class="k">:</span> <span class="kt">Notification</span><span class="o">)</span><span class="k">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="o">{</span>
<span class="n">notification</span> <span class="k">match</span> <span class="o">{</span>
<span class="k">case</span> <span class="nc">Email</span><span class="o">(</span><span class="n">email</span><span class="o">,</span> <span class="n">title</span><span class="o">,</span> <span class="k">_</span><span class="o">)</span> <span class="k">=></span>
<span class="n">s</span><span class="s">"You got an email from $email with title: $title"</span>
<span class="k">case</span> <span class="nc">SMS</span><span class="o">(</span><span class="n">number</span><span class="o">,</span> <span class="n">message</span><span class="o">)</span> <span class="k">=></span>
<span class="n">s</span><span class="s">"You got an SMS from $number! Message: $message"</span>
<span class="k">case</span> <span class="nc">VoiceRecording</span><span class="o">(</span><span class="n">name</span><span class="o">,</span> <span class="n">link</span><span class="o">)</span> <span class="k">=></span>
<span class="n">s</span><span class="s">"you received a Voice Recording from $name! Click the link to hear it: $link"</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">val</span> <span class="nv">someSms</span> <span class="k">=</span> <span class="nc">SMS</span><span class="o">(</span><span class="s">"12345"</span><span class="o">,</span> <span class="s">"Are you there?"</span><span class="o">)</span>
<span class="k">val</span> <span class="nv">someVoiceRecording</span> <span class="k">=</span> <span class="nc">VoiceRecording</span><span class="o">(</span><span class="s">"Tom"</span><span class="o">,</span> <span class="s">"voicerecording.org/id/123"</span><span class="o">)</span>
<span class="nf">println</span><span class="o">(</span><span class="nf">showNotification</span><span class="o">(</span><span class="n">someSms</span><span class="o">))</span> <span class="c1">// prints You got an SMS from 12345! Message: Are you there?</span>
<span class="nf">println</span><span class="o">(</span><span class="nf">showNotification</span><span class="o">(</span><span class="n">someVoiceRecording</span><span class="o">))</span> <span class="c1">// you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123</span>
</code></pre>
<p>I could see implementations being possible off of some of Scala, but case classes aren't as useful outside the context of static types or validations. This means relying on arity or other special contracts to ensure the right thing gets matched, which could be inaccurate at times.</p>
<a name="Wrap-Up"></a>
<h2 >Wrap Up<a href="#Wrap-Up" class="wiki-anchor">¶</a></h2>
<p>Noted that there are several other languages that have features such as this, but I see these as some of the more prominent ones. OCaml, F#, and other languages also feature pattern matching, and would be worth looking into.</p>
<p>Most of Qo's implementation is loosely based off of Scala and Haskell techniques.</p> Ruby master - Feature #14709: Proper pattern matchinghttps://bugs.ruby-lang.org/issues/14709?journal_id=716442018-04-25T19:45:20Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<ul></ul><p>On considering a bit more, I've come up with a few syntax variants for such a feature.</p>
<p>Preference towards avoiding <code>%p</code> and other shorthands in favor of a more self-descriptive word like <code>matches</code> or <code>match</code>.</p>
<a name="1-matches"></a>
<h3 >1. matches<a href="#1-matches" class="wiki-anchor">¶</a></h3>
<p>There are a few variants of this I can think of.</p>
<p><strong>1A - Matches Guard Block</strong></p>
<p>The first involves the guard-block style syntax:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">case</span> <span class="n">value</span>
<span class="k">when</span> <span class="n">matches</span><span class="p">(</span><span class="sr">/Foo/</span><span class="p">,</span> <span class="mi">42</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="nb">name</span><span class="p">,</span> <span class="n">age</span><span class="o">|</span> <span class="p">}</span>
<span class="k">else</span> <span class="n">matches</span> <span class="p">{</span> <span class="o">|</span><span class="n">object</span><span class="o">|</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre>
<p><strong>1B - Matches then local inject</strong></p>
<p>The second would involve the use of <code>then</code> or indent:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">case</span> <span class="n">value</span>
<span class="c1"># First option is to use then as a demarcation like if and case/when:</span>
<span class="k">when</span> <span class="n">matches</span><span class="p">(</span><span class="sr">/Foo/</span><span class="p">,</span> <span class="mi">42</span><span class="p">)</span> <span class="k">then</span> <span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">age</span><span class="p">)</span>
<span class="c1"># alternatively, line break:</span>
<span class="k">when</span> <span class="n">matches</span><span class="p">(</span><span class="sr">/Foo/</span><span class="p">,</span> <span class="mi">42</span><span class="p">)</span>
<span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">age</span><span class="p">)</span>
<span class="k">else</span> <span class="n">matches</span>
<span class="c1"># alternate case</span>
<span class="k">end</span>
</code></pre>
<p>Considerations for this second method are how you would define local variables. Blocks give you control over this, but Ruby does not currently have a clean way to define local variables based off the results of a function.</p>
<p>With something like case classes it'd be a matter of reflection on arity and param names from the initializer, but this would be slow. It'd also be potentially rife with namespace collisions and shadowing by trying to be too clever.</p>
<a name="2-Enumerator-like-yielder"></a>
<h3 >2. Enumerator-like yielder:<a href="#2-Enumerator-like-yielder" class="wiki-anchor">¶</a></h3>
<p>Enumerator has the following syntax which exposes a bind point:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">one_then_two_forever</span> <span class="o">=</span> <span class="no">Enumerator</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">y</span><span class="o">|</span>
<span class="n">y</span><span class="p">.</span><span class="nf">yield</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="kp">loop</span> <span class="p">{</span> <span class="n">y</span><span class="p">.</span><span class="nf">yield</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre>
<p>What if we extrapolate from that?:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">match</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">m</span><span class="o">|</span>
<span class="n">m</span><span class="p">.</span><span class="nf">when</span><span class="p">(</span><span class="sr">/name/</span><span class="p">,</span> <span class="mi">42</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="nb">name</span><span class="p">,</span> <span class="n">age</span><span class="o">|</span> <span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">age</span><span class="p">)</span> <span class="p">}</span>
<span class="n">m</span><span class="p">.</span><span class="nf">else</span> <span class="p">{</span> <span class="o">|</span><span class="n">object</span><span class="o">|</span> <span class="k">raise</span> <span class="s2">"Can't convert!"</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre>
<p>This would be similar to concepts used for benchmark, websockets, async, and other tasks by providing a yielder point. It is also very succinct.</p>
<p>It may be a bit magic, but when passed no argument <code>match</code> could return a <code>Proc</code> instead awaiting a value. That would allow for some really interesting things like this:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">get_url</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="p">.</span><span class="nf">get_response</span><span class="p">(</span><span class="no">URI</span><span class="p">(</span><span class="n">url</span><span class="p">)).</span><span class="nf">then</span><span class="p">(</span><span class="o">&</span><span class="n">match</span> <span class="k">do</span> <span class="o">|</span><span class="n">m</span><span class="o">|</span>
<span class="n">m</span><span class="p">.</span><span class="nf">when</span><span class="p">(</span><span class="no">Net</span><span class="o">::</span><span class="no">HTTPSuccess</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">response</span><span class="o">|</span> <span class="n">response</span><span class="p">.</span><span class="nf">body</span><span class="p">.</span><span class="nf">size</span> <span class="p">}</span>
<span class="n">m</span><span class="p">.</span><span class="nf">else</span> <span class="p">{</span> <span class="o">|</span><span class="n">response</span><span class="o">|</span> <span class="k">raise</span> <span class="n">response</span><span class="p">.</span><span class="nf">message</span> <span class="p">}</span>
<span class="p">))</span>
<span class="k">end</span>
</code></pre>
<p>Any one of the <code>when</code> cases could be used with <code>===</code>, behaving very much like a case statement's <code>when</code>. Right-hand destructuring may still be interesting here though, I'll have to think on that.</p>
<p>This syntax would expose a very minimal changeset to the language itself, requiring only a single word and utilizing already common patterns.</p>
<p><strong>EDIT</strong> - I've just finished an implementation of that variant, and the syntax for the two above examples using it would be:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Qo</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">m</span><span class="o">|</span>
<span class="n">m</span><span class="p">.</span><span class="nf">when</span><span class="p">(</span><span class="sr">/name/</span><span class="p">,</span> <span class="mi">42</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="nb">name</span><span class="p">,</span> <span class="n">age</span><span class="o">|</span> <span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">age</span><span class="p">)</span> <span class="p">}</span>
<span class="n">m</span><span class="p">.</span><span class="nf">else</span> <span class="p">{</span> <span class="o">|</span><span class="n">object</span><span class="o">|</span> <span class="k">raise</span> <span class="s2">"Can't convert!"</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">get_url</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="p">.</span><span class="nf">get_response</span><span class="p">(</span><span class="no">URI</span><span class="p">(</span><span class="n">url</span><span class="p">)).</span><span class="nf">then</span><span class="p">(</span><span class="o">&</span><span class="no">Qo</span><span class="p">.</span><span class="nf">match</span> <span class="k">do</span> <span class="o">|</span><span class="n">m</span><span class="o">|</span>
<span class="n">m</span><span class="p">.</span><span class="nf">when</span><span class="p">(</span><span class="no">Net</span><span class="o">::</span><span class="no">HTTPSuccess</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">response</span><span class="o">|</span> <span class="n">response</span><span class="p">.</span><span class="nf">body</span><span class="p">.</span><span class="nf">size</span> <span class="p">}</span>
<span class="n">m</span><span class="p">.</span><span class="nf">else</span> <span class="p">{</span> <span class="o">|</span><span class="n">response</span><span class="o">|</span> <span class="k">raise</span> <span class="n">response</span><span class="p">.</span><span class="nf">message</span> <span class="p">}</span>
<span class="p">))</span>
<span class="k">end</span>
</code></pre>
<p>So it would appear to be very minimally invasive as far as syntax additions. The code for it is in <code>lib/qo/matchers/pattern_match_block.rb</code>, and I've commented on the differences there.</p> Ruby master - Feature #14709: Proper pattern matchinghttps://bugs.ruby-lang.org/issues/14709?journal_id=716642018-04-27T02:01:55Zjgaskins (Jamie Gaskins)jgaskins@gmail.com
<ul></ul><p>Whenever I feel I need pattern matching, I'm trying to handle method args. With some of the ideas presented here, like Qo, I feel I'd really only be using it inside the outer edges of a method to match on the args that that method received — that is, I'd have to manually pipe the args I received into the pattern matcher every time. I automated that concept in one of my apps, from which I extracted a gem called <a href="https://github.com/jgaskins/method_pattern" class="external">method_pattern</a> (it very slightly predates Qo). The idea behind it, following baweaver's <code>Net::HTTP</code> example:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Client</span>
<span class="kp">extend</span> <span class="no">MethodPattern</span>
<span class="n">defn</span> <span class="ss">:get</span> <span class="k">do</span>
<span class="n">with</span><span class="p">(</span><span class="no">String</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">url</span><span class="o">|</span> <span class="n">request</span><span class="p">(</span><span class="no">URI</span><span class="p">(</span><span class="n">url</span><span class="p">))</span> <span class="p">}</span>
<span class="n">with</span><span class="p">(</span><span class="no">URI</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">uri</span><span class="o">|</span> <span class="n">handle_response</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="p">.</span><span class="nf">get_response</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">defn</span> <span class="ss">:handle_response</span> <span class="k">do</span>
<span class="n">with</span><span class="p">(</span><span class="no">Net</span><span class="o">::</span><span class="no">HTTPOK</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">response</span><span class="o">|</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">body</span><span class="p">)</span> <span class="p">}</span>
<span class="n">with</span><span class="p">(</span><span class="no">Net</span><span class="o">::</span><span class="no">HTTPNotFound</span><span class="p">)</span> <span class="p">{</span> <span class="k">raise</span> <span class="no">ArgumentError</span><span class="p">,</span> <span class="s2">"URL doesn't point to a valid resource"</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>This example is only based on classes, but it also works for values because it's based on the <code>===</code> method, as well — it really is such a great tool for pattern matching. Another example here, for a method <code>def get(status:, headers:, body:)</code>, which also shows how handling keyword args can look (even when matching only a subset of them), and even matching nested hashes as in Yuki Torii's <code>pmruby</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Client</span>
<span class="kp">extend</span> <span class="no">MethodPattern</span>
<span class="n">defn</span> <span class="ss">:get</span> <span class="k">do</span>
<span class="n">with</span><span class="p">(</span><span class="ss">status: </span><span class="mi">200</span><span class="o">...</span><span class="mi">400</span><span class="p">,</span> <span class="ss">headers: </span><span class="p">{</span> <span class="s1">'Content-Type'</span> <span class="o">=></span> <span class="sr">/json/</span> <span class="p">})</span> <span class="k">do</span> <span class="o">|</span><span class="n">body</span><span class="p">:,</span> <span class="o">**|</span>
<span class="n">handle_success</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="ss">symbolize_names: </span><span class="kp">true</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">with</span><span class="p">(</span><span class="ss">status: </span><span class="mi">400</span><span class="o">...</span><span class="mi">500</span><span class="p">,</span> <span class="ss">headers: </span><span class="p">{</span> <span class="s1">'Content-Type'</span> <span class="o">=></span> <span class="sr">/json/</span> <span class="p">})</span> <span class="k">do</span> <span class="o">|</span><span class="n">body</span><span class="p">:,</span> <span class="o">**|</span>
<span class="n">handle_client_error</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="ss">symbolize_names: </span><span class="kp">true</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">with</span><span class="p">(</span><span class="ss">status: </span><span class="mi">500</span><span class="o">..</span><span class="mi">599</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">body</span><span class="p">:,</span> <span class="o">**|</span>
<span class="n">handle_server_error</span> <span class="n">body</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>I don't know what first-class syntax could look like for this, unfortunately, but I imagine it might look similar to whatever ideas people had for type annotations. The hard part is making it work with multiple entries per method.</p> Ruby master - Feature #14709: Proper pattern matchinghttps://bugs.ruby-lang.org/issues/14709?journal_id=716762018-04-27T08:25:29Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<ul></ul><p>I've gone and done something exceptionally bad to Ruby, but this should be an interesting proof of concept nonetheless:</p>
<ul>
<li>Benchmarks - <a href="https://github.com/baweaver/qo/blob/evil/Rakefile" class="external">https://github.com/baweaver/qo/blob/evil/Rakefile</a>
</li>
<li>Results - <a href="https://gist.github.com/baweaver/0869255d7325000c4c9bca5c836aef3c" class="external">https://gist.github.com/baweaver/0869255d7325000c4c9bca5c836aef3c</a>
</li>
</ul>
<p><code>Qo::Evil</code> runs within decent speed parity of vanilla Ruby on arrays of any significant size (need to qualify, but around 100+ I'd guess), and faster than any dynamic style vanilla Ruby.</p>
<p>The current branch is super messy, but it's an interesting idea:</p>
<p><a href="https://github.com/baweaver/qo/blob/evil/lib/qo/evil/matcher.rb" class="external">https://github.com/baweaver/qo/blob/evil/lib/qo/evil/matcher.rb</a></p>
<p>Essentially take a list of matchers and "compile" them into a proc using eval and some caching against a list of safe values, binding any non-coercibles to a local variable stack that gets set to the binder.</p>
<p>Be aware, this is very much black magic, but it's black magic that very much amuses me as it means something quite fun: It's possible to make pattern matches run at close (~1.2 - 1.5x) to optimal vanilla Ruby speed without even touching C code.</p> Ruby master - Feature #14709: Proper pattern matchinghttps://bugs.ruby-lang.org/issues/14709?journal_id=725652018-06-21T08:36:07Zmatz (Yukihiro Matsumoto)matz@ruby.or.jp
<ul><li><strong>Status</strong> changed from <i>Open</i> to <i>Closed</i></li></ul><p>We are not going to add the pattern matcher proposed in the OP (that uses <code>%p</code>), because it is a mere prototype. Yuki told us so clearly. If we were going to add pattern matching in Ruby, we should add it with better syntax.</p>
<p>Regarding Qo, I like the basic idea, but similarly, we should consider new (and good looking) syntax.<br>
The problem is that I have no idea for an excellent syntax for the pattern matching right now.<br>
I close this issue and expect the proposal for a new syntax.</p>
<p>Matz.</p> Ruby master - Feature #14709: Proper pattern matchinghttps://bugs.ruby-lang.org/issues/14709?journal_id=726732018-06-27T08:18:42Zshevegen (Robert A. Heiler)shevegen@gmail.com
<ul></ul><p>zverok wrote an add-on in regards to "syntax proposal" here:</p>
<p><a href="https://zverok.github.io/blog/2018-06-26-pattern-matching.html" class="external">https://zverok.github.io/blog/2018-06-26-pattern-matching.html</a></p>
<p>I don't have a suggestion for a better syntax myself really.</p>
<p>zverok suggested, among other examples, e. g. this:</p>
<pre><code>case (lat, *lng)
when (Numeric, Numeric => lng, Hash => opts)
</code></pre>
<p>I am not a huge fan of the (). :P</p>
<p>However had, I was also thinking ... pattern matching is in<br>
some ways similar to regex-checking, yes? For regexes we<br>
have =~.</p>
<p>Perhaps we could use a similar construct for pattern matching,<br>
also starting with =. Not sure about the second character.</p>
<p>=| might be almost an obvious choice since it looks like a<br>
pipe in a way, but I am not sure if this can be used.</p>
<p>One could also ponder about a combination of three characters,<br>
a bit like the somewhat new one used for lambdas. ->| or<br>
->~ ... not that any of these are very pretty. And one thing<br>
I also always mentioned is that it should not be too hard to<br>
visually differentiate between different things in ruby code<br>
so perhaps something like ->~ is a bad idea since it is not<br>
so easy to see any difference. But two characters may be fine,<br>
like the regex variant or also the compound ones like += or<br>
-= (two characters seem better than more than two characters).</p> Ruby master - Feature #14709: Proper pattern matchinghttps://bugs.ruby-lang.org/issues/14709?journal_id=732832018-08-01T14:32:35Zktsj (Kazuki Tsujimoto)kazuki@callcc.net
<ul><li><strong>Related to</strong> <i><a class="issue tracker-2 status-5 priority-4 priority-default closed" href="/issues/14912">Feature #14912</a>: Introduce pattern matching syntax</i> added</li></ul>