Ruby Issue Tracking System: Issueshttps://bugs.ruby-lang.org/https://bugs.ruby-lang.org/favicon.ico?17113305112024-03-19T17:58:11ZRuby Issue Tracking System
Redmine Ruby master - Feature #20349 (Open): Pattern Matching - Expose local variable captureshttps://bugs.ruby-lang.org/issues/203492024-03-19T17:58:11Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>In Regular Expressions we have the ability to utilize <code>Regexp.last_match</code> (<a href="https://ruby-doc.org/3.2.2/Regexp.html#method-c-last_match" class="external">link</a>) to access the most recent match data from a regular expression match. I would like to propose that we have similar functionality for pattern matching:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="nb">require</span> <span class="s2">"rubocop"</span>
<span class="k">def</span> <span class="nf">ast_from</span><span class="p">(</span><span class="n">string</span><span class="p">)</span>
<span class="k">case</span> <span class="n">string</span>
<span class="k">when</span> <span class="no">String</span> <span class="k">then</span> <span class="n">processed_source_from</span><span class="p">(</span><span class="n">string</span><span class="p">).</span><span class="nf">ast</span>
<span class="k">when</span> <span class="no">RuboCop</span><span class="o">::</span><span class="no">ProcessedSource</span> <span class="k">then</span> <span class="n">string</span><span class="p">.</span><span class="nf">ast</span>
<span class="k">when</span> <span class="no">RuboCop</span><span class="o">::</span><span class="no">AST</span><span class="o">::</span><span class="no">Node</span> <span class="k">then</span> <span class="n">string</span>
<span class="k">else</span> <span class="kp">nil</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">longform_block?</span><span class="p">(</span><span class="n">node</span><span class="p">)</span>
<span class="n">node</span> <span class="k">in</span> <span class="p">[</span><span class="ss">:block</span><span class="p">,</span> <span class="c1"># s(:block,</span>
<span class="p">[</span><span class="ss">:send</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">target_method</span><span class="p">],</span> <span class="c1"># s(:send, s(:array), :select),</span>
<span class="p">[[</span><span class="ss">:arg</span><span class="p">,</span> <span class="n">v</span><span class="p">]],</span> <span class="c1"># s(:args, s(:arg, :v)),</span>
<span class="p">[</span><span class="ss">:send</span><span class="p">,</span> <span class="p">[</span><span class="ss">:lvar</span><span class="p">,</span> <span class="o">^</span><span class="n">v</span><span class="p">],</span> <span class="n">block_method</span><span class="p">]</span> <span class="c1"># s(:send, s(:lvar, :v), :even?))</span>
<span class="p">]</span>
<span class="k">end</span>
<span class="n">longform_block?</span><span class="p">(</span><span class="n">ast_from</span><span class="p">(</span><span class="s2">"[1, 2, 3].select { |v| v.even? }"</span><span class="p">))</span>
<span class="c1"># => true</span>
<span class="c1"># Name is not important, idea is, name can be debated. `source` is used to not</span>
<span class="c1"># show AST nodes</span>
<span class="no">PatternMatch</span><span class="p">.</span><span class="nf">last_match</span><span class="p">.</span><span class="nf">transform_values</span><span class="p">(</span><span class="o">&</span><span class="ss">:source</span><span class="p">)</span>
<span class="c1"># => {:node=>"[1, 2, 3].select { |v| v.even? }",</span>
<span class="c1"># :result=>"[1, 2, 3].select { |v| v.even? }",</span>
<span class="c1"># :target=>"[1, 2, 3]",</span>
<span class="c1"># :target_method=>:select,</span>
<span class="c1"># :v=>:v,</span>
<span class="c1"># :block_method=>:even?}</span>
<span class="c1"># Hacky version to show how / where behavior could be captured</span>
<span class="k">def</span> <span class="nf">longform_block?</span><span class="p">(</span><span class="n">node</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">node</span> <span class="k">in</span> <span class="p">[</span><span class="ss">:block</span><span class="p">,</span> <span class="c1"># s(:block,</span>
<span class="p">[</span><span class="ss">:send</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">target_method</span><span class="p">],</span> <span class="c1"># s(:send, s(:array), :select),</span>
<span class="p">[[</span><span class="ss">:arg</span><span class="p">,</span> <span class="n">v</span><span class="p">]],</span> <span class="c1"># s(:args, s(:arg, :v)),</span>
<span class="p">[</span><span class="ss">:send</span><span class="p">,</span> <span class="p">[</span><span class="ss">:lvar</span><span class="p">,</span> <span class="o">^</span><span class="n">v</span><span class="p">],</span> <span class="n">block_method</span><span class="p">]</span> <span class="c1"># s(:send, s(:lvar, :v), :even?))</span>
<span class="p">]</span>
<span class="n">pp</span><span class="p">(</span><span class="nb">binding</span><span class="p">.</span><span class="nf">local_variables</span><span class="p">.</span><span class="nf">to_h</span> <span class="p">{</span> <span class="p">[</span><span class="n">_1</span><span class="p">,</span> <span class="nb">binding</span><span class="p">.</span><span class="nf">local_variable_get</span><span class="p">(</span><span class="n">_1</span><span class="p">).</span><span class="nf">then</span> <span class="p">{</span> <span class="o">|</span><span class="n">s</span><span class="o">|</span> <span class="n">s</span><span class="p">.</span><span class="nf">source</span> <span class="k">rescue</span> <span class="n">s</span> <span class="p">}]</span> <span class="p">})</span>
<span class="n">result</span>
<span class="k">end</span>
</code></pre> Ruby master - Misc #19692 (Open): Net::HTTP Performance Workstreamhttps://bugs.ruby-lang.org/issues/196922023-05-24T20:21:17Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>While working on identifying causes behind Capybara test slowness I had noticed some memory profiles pointed at a few slow code paths in <code>Net::HTTP</code>, one of which I've already opened a PR for here:</p>
<p><a href="https://github.com/ruby/net-http/pull/140" class="external">https://github.com/ruby/net-http/pull/140</a></p>
<p>The reason I'm opening this ticket is that I had found some additional areas which warrant investigation for some decent performance gains in frequently run sections of code in <code>Net::HTTP</code> and to ask if someone would be willing to work with me on opening likely several PRs against the code base in the near future.</p>
<p>I will hold myself to the standard of qualifying these changes with performance data on which parts are frequently run and are slow, but would appreciate working with someone from core through this.</p> Ruby master - Feature #19634 (Open): Pattern matching dynamic keyhttps://bugs.ruby-lang.org/issues/196342023-05-09T19:05:19Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>I found myself in a situation (<a href="https://rosettacode.org/wiki/Stable_marriage_problem#top-page" class="external">stable marriage problem</a>) where I would like to match against a dynamic key, like so:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">mentor_proposals</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">mentor_name: </span><span class="p">[</span><span class="s1">'mentee_1'</span><span class="p">,</span> <span class="s1">'mentee_2'</span><span class="p">]</span> <span class="p">}</span>
<span class="n">mentor_name</span> <span class="o">=</span> <span class="ss">:mentor_name</span>
<span class="n">mentee_name</span> <span class="o">=</span> <span class="s1">'mentee_1'</span>
<span class="n">mentor_proposals</span> <span class="k">in</span> <span class="o">^</span><span class="ss">key: </span><span class="p">[</span><span class="o">*</span><span class="p">,</span> <span class="o">^</span><span class="n">mentee_name</span><span class="p">,</span> <span class="o">*</span><span class="p">]</span> <span class="c1"># SyntaxError</span>
</code></pre>
<p>Currently this is not supported syntax, but there are some use cases in which I might see myself wanting to use it including this one. As <code>deconstruct_keys</code> currently accepts an <code>Array</code> of keys this would not break compatibility but would introduce syntactic complexity in capturing keys on hash-like matches.</p>
<p>I believe the tradeoff is worthwhile, but would like to hear others opinions on the matter.</p>
<p>Granted this case has some oddities of <code>Symbol</code> and <code>String</code> interchangeability as an implementation detail, and I will not be arguing for key irreverence in this issue as that's a <a href="https://dev.to/baweaver/the-case-for-pattern-matching-key-irreverence-in-ruby-1oll" class="external">much more involved topic</a>.</p> Ruby master - Bug #19080 (Rejected): String Range inclusion using `===` brokenhttps://bugs.ruby-lang.org/issues/190802022-10-24T05:00:59Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>When using <code>===</code> implicitly for a pattern match I noticed a behavior which I believe is a bug in Ruby. <code>===</code> does not match with <code>include?</code> for ranges of <code>String</code> types, but also does so inconsistently:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">range</span> <span class="o">=</span> <span class="s1">'0'</span><span class="o">..</span><span class="s1">'255'</span>
<span class="p">(</span><span class="s1">'1'</span><span class="o">..</span><span class="s1">'5'</span><span class="p">).</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">v</span><span class="o">|</span>
<span class="p">{</span>
<span class="ss">v: </span><span class="n">v</span><span class="p">,</span>
<span class="ss">teq: </span><span class="n">range</span> <span class="o">===</span> <span class="n">v</span><span class="p">,</span>
<span class="ss">include?: </span><span class="n">range</span><span class="p">.</span><span class="nf">include?</span><span class="p">(</span><span class="n">v</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">end</span>
<span class="c1"># => [</span>
<span class="c1"># { v: "1", teq: true, include?: true },</span>
<span class="c1"># { v: "2", teq: true, include?: true },</span>
<span class="c1"># { v: "3", teq: false, include?: true },</span>
<span class="c1"># { v: "4", teq: false, include?: true },</span>
<span class="c1"># { v: "5", teq: false, include?: true }</span>
<span class="c1"># ]</span>
<span class="c1"># But numbers work??</span>
<span class="n">range</span> <span class="o">=</span> <span class="mi">0</span><span class="o">..</span><span class="mi">255</span>
<span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">5</span><span class="p">).</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">v</span><span class="o">|</span>
<span class="p">{</span>
<span class="ss">v: </span><span class="n">v</span><span class="p">,</span>
<span class="ss">teq: </span><span class="n">range</span> <span class="o">===</span> <span class="n">v</span><span class="p">,</span>
<span class="ss">include?: </span><span class="n">range</span><span class="p">.</span><span class="nf">include?</span><span class="p">(</span><span class="n">v</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">end</span>
<span class="c1"># => [</span>
<span class="c1"># { v: 1, teq: true, include?: true},</span>
<span class="c1"># { v: 2, teq: true, include?: true},</span>
<span class="c1"># { v: 3, teq: true, include?: true},</span>
<span class="c1"># { v: 4, teq: true, include?: true},</span>
<span class="c1"># { v: 5, teq: true, include?: true}</span>
<span class="c1"># ]</span>
</code></pre>
<p>Am I doing something wrong? This does not feel like it is working as expected. I might dig through the C code to see how this is implemented and why this happens, but am currently confused by it.</p> Ruby master - Feature #19063 (Rejected): Hash.new with non-value objects should be less confusinghttps://bugs.ruby-lang.org/issues/190632022-10-16T22:24:24Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>Related to <a class="issue tracker-1 status-6 priority-4 priority-default closed" title="Bug: Assigning default value for a Hash as an empty Array creating unpredictable results (Rejected)" href="https://bugs.ruby-lang.org/issues/10713">#10713</a> and <a class="issue tracker-1 status-6 priority-4 priority-default closed" title="Bug: Hash.new inconsistency when initialized with an enumerable (Rejected)" href="https://bugs.ruby-lang.org/issues/2764">#2764</a>.</p>
<p>Ruby's <code>Hash.new</code> accepts either a block or a param for its default value. In the case of non-value objects this leads to unexpected behaviors:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">bad_hash_with_array_values</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="n">good_hash_with_array_values</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="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="p">[]</span> <span class="p">}</span>
</code></pre>
<p>While, as <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/572">@hsbt (Hiroshi SHIBATA)</a> has said in the past, this is behaving as intended for the Ruby language it has caused a lot of confusion in the community over the years and is a known sharp-edge.</p>
<p>My assertion is that this is not the intended behavior, and I cannot find a legitimate usecase in which someone intends for this to happen. More often new users to Ruby are confused by this behavior and spend a lot of time debugging.</p>
<p>We must consider the impact to Ruby users, despite what the intent of the language is, and make the language more clear where possible.</p>
<p>Given that, I have a few potential proposals for Ruby committers.</p>
<a name="Proposal-1-Do-What-They-Meant"></a>
<h3 >Proposal 1: Do What They Meant<a href="#Proposal-1-Do-What-They-Meant" class="wiki-anchor">¶</a></h3>
<p>When people use <code>Hash.new([])</code> they mean <code>Hash.new { |h, k| h[k] = [] }</code>. Can we make that the case that if you pass a mutable or non-value object that the behavior will be as intended using <code>dup</code> or other techniques?</p>
<p>When used in the above incorrect way it is likely if not always broken code.</p>
<a name="Proposal-2-Warn-About-Unexpected-Behavior"></a>
<h3 >Proposal 2: Warn About Unexpected Behavior<a href="#Proposal-2-Warn-About-Unexpected-Behavior" class="wiki-anchor">¶</a></h3>
<p>As mentioned above, I do not believe there are legitimate usages of <code>Hash.new([])</code>, and it is a known bug to many users as they do not intend for that behavior. It may be worthwhile to warn people if they do use it.</p>
<a name="Proposal-3-Require-Frozen-or-Values"></a>
<h3 >Proposal 3: Require Frozen or Values<a href="#Proposal-3-Require-Frozen-or-Values" class="wiki-anchor">¶</a></h3>
<p>This is more breaking than the above, but it may make sense to require any value passed to <code>Hash.new</code> to either be <code>frozen</code> or a value object (e.g. <code>1</code> or <code>true</code>)</p>
<a name="Updating-RuboCop"></a>
<h2 >Updating RuboCop<a href="#Updating-RuboCop" class="wiki-anchor">¶</a></h2>
<p>Failing this, I am considering advocating for RuboCop and similar linters to warn people against this behavior as it is not intended in most to all cases:</p>
<p><a href="https://github.com/rubocop/rubocop/issues/11013" class="external">https://github.com/rubocop/rubocop/issues/11013</a></p>
<p>...but as <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/3344">@ioquatix (Samuel Williams)</a> has mentioned on the issue it would make more sense to fix Ruby rather than put a patch on top of it. I would be inclined to agree with his assessment, and would rather fix this at a language level as it is a known point of confusion.</p>
<a name="Final-Thoughts"></a>
<h2 >Final Thoughts<a href="#Final-Thoughts" class="wiki-anchor">¶</a></h2>
<p>I would ask that maintainers consider the confusion that this has caused in the community, rather than asserting this "works as intended." It does work as intended, but the intended functionality can make Ruby more difficult for beginners. We should keep this in mind.</p> Ruby master - Bug #19033 (Rejected): One-liner pattern match as Boolean arg syntax errorhttps://bugs.ruby-lang.org/issues/190332022-09-30T22:03:07Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>I was chatting earlier with Seb Wilgosz about pattern matching in tests, and suggested that he might consider the following:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">expect</span> <span class="n">result</span> <span class="k">in</span> <span class="n">pattern</span>
</code></pre>
<p>...but he reported back this will syntax error, including with parens:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">res</span> <span class="o">=</span> <span class="p">[</span><span class="ss">:not_found</span><span class="p">,</span> <span class="mi">999</span><span class="p">]</span>
<span class="n">expect</span><span class="p">(</span><span class="n">res</span> <span class="k">in</span> <span class="p">[</span><span class="ss">:not_found</span><span class="p">,</span> <span class="o">*</span><span class="n">payload</span><span class="p">])</span>
<span class="c1"># => SyntaxError:</span>
<span class="c1"># /spec/app/interactors/articles/publish_spec.rb:13: syntax error, unexpected `in', expecting ')'</span>
</code></pre>
<p>Interestingly though the following work:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">res</span> <span class="o">=</span> <span class="p">[</span><span class="ss">:not_found</span><span class="p">,</span> <span class="mi">999</span><span class="p">]</span>
<span class="n">expect</span><span class="p">(</span><span class="n">res</span><span class="p">)</span> <span class="k">in</span><span class="p">([</span><span class="ss">:not_found</span><span class="p">,</span> <span class="o">*</span><span class="n">payload</span><span class="p">])</span>
<span class="c1"># 1 example, 0 failures</span>
<span class="n">expect</span><span class="p">(</span><span class="n">res</span><span class="p">)</span> <span class="k">in</span><span class="p">([</span><span class="ss">:not_found</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">])</span>
<span class="c1"># 1 example, 0 failures</span>
</code></pre>
<p>While this appears like an RSpec issue I would contend that it is reproducible with any other method that takes a boolean-like argument.</p>
<p>For me this feels like a syntax bug, but could see a case where it may be interpreted as ambiguous depending on the precedence of <code>in</code> relative to method arguments much like <code>method_name value if condition</code> is vague between <code>method_name(value) if condition</code> and <code>method_name(value if condition)</code>. That'll be especially difficult if it's <code>method_name value in pattern if condition</code>, so I do not envy parser writers here.</p>
<p>Would be curious for thoughts on that, or if we're looking at that wrong.</p> Ruby master - Feature #18821 (Open): Expose Pattern Matching interfaces in core classeshttps://bugs.ruby-lang.org/issues/188212022-06-08T06:07:36Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<a name="Problem-Statement"></a>
<h2 >Problem Statement<a href="#Problem-Statement" class="wiki-anchor">¶</a></h2>
<p>Pattern matching is an exceptionally powerful feature in modern versions of Ruby, but it has one critical weakness that we should discuss:</p>
<p>It is only as powerful as the number of classes which implement its interfaces.</p>
<p>The more common these interfaces become, the more powerful pattern matching will become for everyday use in any scenario.</p>
<a name="Areas-of-Attention"></a>
<h2 >Areas of Attention<a href="#Areas-of-Attention" class="wiki-anchor">¶</a></h2>
<p>That said, what are some classes in core Ruby where it may make sense to implement pattern matching interfaces, and what do we gain from them? I will provide an abbreviated list, but can look to qualify a larger list of potentials if this is of interest.</p>
<a name="Set"></a>
<h3 >Set<a href="#Set" class="wiki-anchor">¶</a></h3>
<p>Currently <code>Set</code> does not implement <code>deconstruct</code>. Especially <code>Enumerable</code>-like and <code>Array</code> like entities make sense here:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># Hypothetical implementation</span>
<span class="k">class</span> <span class="nc">Set</span>
<span class="kp">alias_method</span> <span class="ss">:deconstruct</span><span class="p">,</span> <span class="ss">:to_a</span>
<span class="k">end</span>
<span class="no">Set</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="k">in</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="o">*</span><span class="p">]</span>
<span class="c1"># => true</span>
</code></pre>
<a name="Matrix"></a>
<h3 >Matrix<a href="#Matrix" class="wiki-anchor">¶</a></h3>
<p>Speaking of Array-like structures, Matrix may make sense as well:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Matrix</span>
<span class="kp">alias_method</span> <span class="ss">:deconstruct</span><span class="p">,</span> <span class="ss">:to_a</span>
<span class="k">end</span>
<span class="c1"># => :deconstruct</span>
<span class="no">Matrix</span><span class="p">[[</span><span class="mi">25</span><span class="p">,</span> <span class="mi">93</span><span class="p">],</span> <span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">66</span><span class="p">]]</span> <span class="k">in</span> <span class="p">[[</span><span class="mi">20</span><span class="o">..</span><span class="mi">30</span><span class="p">,</span> <span class="n">_</span><span class="p">],</span> <span class="p">[</span><span class="o">..</span><span class="mi">0</span><span class="p">,</span> <span class="n">_</span><span class="p">]]</span>
<span class="c1"># => true</span>
</code></pre>
<a name="CSV"></a>
<h3 >CSV<a href="#CSV" class="wiki-anchor">¶</a></h3>
<p>In the case of headers especially this can become very powerful with <code>deconstruct_keys</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="nb">require</span> <span class="s2">"csv"</span>
<span class="nb">require</span> <span class="s2">"net/http"</span>
<span class="nb">require</span> <span class="s2">"json"</span>
<span class="c1"># Hypothetical implementation</span>
<span class="k">class</span> <span class="nc">CSV::Row</span>
<span class="k">def</span> <span class="nf">deconstruct_keys</span><span class="p">(</span><span class="n">keys</span><span class="p">)</span>
<span class="c1"># Symbol/String is contentious, yes, I will address in a moment</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">to_h</span><span class="p">.</span><span class="nf">transform_keys</span><span class="p">(</span><span class="o">&</span><span class="ss">:to_sym</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># Creating some sample data for example:</span>
<span class="n">json_data</span> <span class="o">=</span> <span class="no">URI</span><span class="p">(</span><span class="s2">"https://jsonplaceholder.typicode.com/todos"</span><span class="p">)</span>
<span class="p">.</span><span class="nf">then</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</span><span class="p">(</span><span class="n">_1</span><span class="p">)</span> <span class="p">}</span>
<span class="p">.</span><span class="nf">then</span> <span class="p">{</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">_1</span><span class="p">,</span> <span class="ss">symbolize_names: </span><span class="kp">true</span><span class="p">)</span> <span class="p">}</span>
<span class="n">headers</span> <span class="o">=</span> <span class="n">json_data</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">keys</span>
<span class="n">rows</span> <span class="o">=</span> <span class="n">json_data</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:values</span><span class="p">)</span>
<span class="c1"># Yes yes, hacky</span>
<span class="n">csv_data</span> <span class="o">=</span> <span class="no">CSV</span><span class="p">.</span><span class="nf">generate</span> <span class="k">do</span> <span class="o">|</span><span class="n">csv</span><span class="o">|</span>
<span class="n">csv</span> <span class="o"><<</span> <span class="n">headers</span>
<span class="n">rows</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="n">csv</span> <span class="o"><<</span> <span class="n">_1</span> <span class="p">}</span>
<span class="k">end</span><span class="p">.</span><span class="nf">then</span> <span class="p">{</span> <span class="no">CSV</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">_1</span><span class="p">,</span> <span class="ss">headers: </span><span class="kp">true</span><span class="p">)</span> <span class="p">}</span>
<span class="c1"># But can provide very interesting results:</span>
<span class="n">csv_data</span><span class="p">.</span><span class="nf">select</span> <span class="p">{</span> <span class="n">_1</span> <span class="k">in</span> <span class="ss">userId: </span><span class="s2">"1"</span><span class="p">,</span> <span class="ss">completed: </span><span class="s2">"true"</span> <span class="p">}.</span><span class="nf">size</span>
<span class="c1"># => 11</span>
</code></pre>
<p>Though this one does raise the broader question of the conflation of Symbol and String keys for our convenience. Given that Ruby has a habit of coercing between the two in other cases I do not find this to be against the spirit of Ruby.</p>
<a name="RegExp-MatchData"></a>
<h3 >RegExp MatchData<a href="#RegExp-MatchData" class="wiki-anchor">¶</a></h3>
<p>In a similar line of thinking to the CSV I believe this would present interesting opportunities, though does raise the question of what to do with <code>nil</code> types (perhaps return <code>[]</code> and <code>{}</code> respectively? May be hacky though)</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">MatchData</span>
<span class="kp">alias_method</span> <span class="ss">:deconstruct</span><span class="p">,</span> <span class="ss">:to_a</span>
<span class="k">def</span> <span class="nf">deconstruct_keys</span><span class="p">(</span><span class="n">keys</span><span class="p">)</span>
<span class="n">named_captures</span><span class="p">.</span><span class="nf">transform_keys</span><span class="p">(</span><span class="o">&</span><span class="ss">:to_sym</span><span class="p">).</span><span class="nf">slice</span><span class="p">(</span><span class="o">*</span><span class="n">keys</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="no">IP_REGEX</span> <span class="o">=</span> <span class="sr">/
(?<first_octet>\d{1,3})\.
(?<second_octet>\d{1,3})\.
(?<third_octet>\d{1,3})\.
(?<fourth_octet>\d{1,3})
/x</span>
<span class="s1">'192.168.1.1'</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="no">IP_REGEX</span><span class="p">)</span> <span class="k">in</span> <span class="p">{</span>
<span class="ss">first_octet: </span><span class="s1">'198'</span><span class="p">,</span>
<span class="ss">fourth_octet: </span><span class="s1">'1'</span>
<span class="p">}</span>
<span class="c1"># => true</span>
</code></pre>
<p>As with before though, we do risk setting a precedent on the conflation of Symbol and String keys when it is convenient to us, so may be worth proceeding with caution there.</p>
<a name="OpenStruct"></a>
<h3 >OpenStruct<a href="#OpenStruct" class="wiki-anchor">¶</a></h3>
<p>Much like <code>Struct</code> I believe there's a good case to make here:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">OpenStruct</span>
<span class="k">def</span> <span class="nf">deconstruct_keys</span><span class="p">(</span><span class="n">keys</span><span class="p">)</span> <span class="o">=</span> <span class="n">keys</span> <span class="p">?</span> <span class="n">to_h</span><span class="p">.</span><span class="nf">slice</span><span class="p">(</span><span class="o">*</span><span class="n">keys</span><span class="p">)</span> <span class="p">:</span> <span class="n">to_h</span>
<span class="k">end</span>
<span class="n">me</span> <span class="o">=</span> <span class="no">OpenStruct</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">name: </span><span class="s1">'Brandon'</span><span class="p">,</span> <span class="ss">age: </span><span class="mi">31</span><span class="p">)</span>
<span class="n">me</span> <span class="k">in</span> <span class="p">{</span> <span class="ss">name: </span><span class="sr">/^B/</span> <span class="p">}</span>
<span class="c1"># => true</span>
</code></pre>
<a name="Other-Thoughts"></a>
<h2 >Other Thoughts<a href="#Other-Thoughts" class="wiki-anchor">¶</a></h2>
<p>I believe there is great potential in the core of Ruby to spread the pattern matching interface. The more common it becomes the more useful it will be to users.</p>
<p>Especially if this were to be adopted into places like Rack, Net::HTTP, JSON, and other areas where frequently more imperative deconstructions and querying are already commonly used.</p>
<p>I bring this up, rather than opening PRs, as I would like to see whether or not the core Ruby team is interested in these types of PRs and work of finding where else these interfaces may make sense.</p>
<p>If you would like my more complete thoughts on this, and considerations for pattern matching interfaces in Ruby, I had written <a href="https://docs.google.com/document/d/1spnuQTKy5i7Lx-sDCsORKN981Iam1IuNdOsYkhh9Yi0/edit#" class="external">Pattern Matching Interfaces in Ruby</a> some time ago to note concerns, potential guidelines, and other considerations.</p> Ruby master - Feature #18384 (Open): Pattern Match Objecthttps://bugs.ruby-lang.org/issues/183842021-12-03T07:05:32Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>Related to discussion in <a class="issue tracker-2 status-1 priority-4 priority-default" title="Feature: users.detect(:name, "Dorian") as shorthand for users.detect { |user| user.name == "Dorian" } (Open)" href="https://bugs.ruby-lang.org/issues/18369">#18369</a> it might be nice to have a literal syntax for constructing a single pattern match case outside of a one-liner.</p>
<p>Years ago in Qo I had done this via <code>===</code> to enable syntax like this:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">list_of_people</span><span class="p">.</span><span class="nf">select</span><span class="p">(</span><span class="o">&</span><span class="no">Qo</span><span class="p">[</span><span class="ss">first_name: </span><span class="sr">/^F/</span><span class="p">,</span> <span class="ss">last_name: </span><span class="sr">/r$/</span><span class="p">,</span> <span class="ss">age: </span><span class="mi">20</span><span class="o">..</span><span class="mi">40</span><span class="p">])</span>
</code></pre>
<p>This is valid Ruby, but the pattern match syntax itself cannot be used outside of a literal match, making this impossible without syntax changes.</p>
<a name="Proposal"></a>
<h3 >Proposal<a href="#Proposal" class="wiki-anchor">¶</a></h3>
<p>My proposal would be a case which would be very useful in predicate methods (<code>any?</code>, <code>all?</code>, etc) and triple-equals responding methods (<code>select</code>, <code>reject</code>, <code>grep</code>, etc):</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">list_of_people</span><span class="p">.</span><span class="nf">select</span><span class="p">(</span><span class="o">&</span><span class="n">pattern</span><span class="p">(</span>
<span class="ss">first_name: </span><span class="sr">/^F/</span><span class="p">,</span>
<span class="ss">last_name: </span><span class="sr">/r$/</span><span class="p">,</span>
<span class="ss">age: </span><span class="mi">20</span><span class="o">..</span><span class="mi">40</span>
<span class="p">))</span>
</code></pre>
<p>...in which <code>pattern</code> would be substituted with a more appropriate name which I cannot think of at the moment.</p>
<a name="Portability"></a>
<h3 >Portability<a href="#Portability" class="wiki-anchor">¶</a></h3>
<p>Now the reason I think this could be very interesting is the portability of patterns. Consider the potential of making a <code>PatternMatch</code> object much like a Regular Expression:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">TARGET_PERSON</span> <span class="o">=</span> <span class="no">PatternMatch</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s1">'something'</span><span class="p">)</span>
<span class="n">list_of_people</span><span class="p">.</span><span class="nf">select</span><span class="p">(</span><span class="o">&</span><span class="no">TARGET_PERSON</span><span class="p">)</span>
</code></pre>
<p>As they can serve similar purposes of giving an expressive language to query against known structures I can see this making sense. The challenge is that the initialization of such an object would need to be special to accommodate the pattern matching syntax, adding more complicated parsing rules.</p>
<p>This behavior might be consistent with <code>Proc</code>, <code>RegExp</code>, and <code>Range</code>-like behavior.</p>
<a name="ActiveRecord-like"></a>
<h3 >ActiveRecord-like<a href="#ActiveRecord-like" class="wiki-anchor">¶</a></h3>
<p>This gets very close to the classic ActiveRecord <code>where</code> pattern:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">People</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">age: </span><span class="mi">20</span><span class="o">..</span><span class="mi">30</span><span class="p">)</span>
</code></pre>
<a name="Potential-Issues"></a>
<h3 >Potential Issues<a href="#Potential-Issues" class="wiki-anchor">¶</a></h3>
<p>Now this is not without potential issue, as must be highlighted. The first, as just mentioned, is the ActiveRecord syntax and potentially overloading that and keyword arguments:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">People</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">age: </span><span class="mi">20</span><span class="o">..</span><span class="mi">30</span><span class="p">)</span>
</code></pre>
<p>Without a clear signifier this could make parsing much more difficult.</p>
<a name="Current-Viable-Workarounds"></a>
<h3 >Current Viable Workarounds<a href="#Current-Viable-Workarounds" class="wiki-anchor">¶</a></h3>
<p>It also must be mentioned that this is currently possible:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">list_of_people</span><span class="p">.</span><span class="nf">select</span> <span class="p">{</span> <span class="n">_1</span> <span class="k">in</span> <span class="p">{</span> <span class="ss">first_name: </span><span class="s1">'something'</span> <span class="p">}</span> <span class="p">}</span>
</code></pre>
<p>...though the requirement of explicit braces feels a tinge verbose, I understand why they're present.</p>
<p>I think this is an acceptable compromise at the moment, but feel we're very close to an interesting syntactic breakthrough.</p> Ruby master - Bug #17596 (Rejected): Calling parameters on `new` method results in :rest and -1 a...https://bugs.ruby-lang.org/issues/175962021-01-30T08:45:46Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>Consider the following class:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Testing</span>
<span class="k">def</span> <span class="nf">initialize</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="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>...and the following call:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">m</span> <span class="o">=</span> <span class="no">Testing</span><span class="p">.</span><span class="nf">method</span><span class="p">(</span><span class="ss">:new</span><span class="p">)</span>
<span class="nb">p</span> <span class="ss">arity: </span><span class="n">m</span><span class="p">.</span><span class="nf">arity</span><span class="p">,</span> <span class="ss">params: </span><span class="n">m</span><span class="p">.</span><span class="nf">parameters</span>
</code></pre>
<p><strong>Expected</strong></p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="p">{</span> <span class="ss">arity: </span><span class="mi">3</span><span class="p">,</span> <span class="ss">params: </span><span class="p">[[</span><span class="ss">:req</span><span class="p">,</span> <span class="ss">:a</span><span class="p">],</span> <span class="p">[</span><span class="ss">:req</span><span class="p">,</span> <span class="ss">:b</span><span class="p">],</span> <span class="p">[</span><span class="ss">:req</span><span class="p">,</span> <span class="ss">:c</span><span class="p">]]</span> <span class="p">}</span>
</code></pre>
<p><strong>Actual</strong></p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="p">{</span> <span class="ss">arity: </span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="ss">params: </span><span class="p">[[</span><span class="ss">:rest</span><span class="p">]]</span> <span class="p">}</span>
</code></pre>
<p>This is confusing to me as I would expect to be able to see the parameters of the <code>new</code> method like this. The same applies for `initialize.</p>
<p>I was experimenting with pattern matching interfaces and dynamically defining class constructor definitions as such:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">deconstruct</span>
<span class="nb">method</span><span class="p">(</span><span class="ss">:new</span><span class="p">).</span><span class="nf">parameters</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="n">public_send</span><span class="p">(</span><span class="n">_1</span><span class="p">.</span><span class="nf">last</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre>
<p>While this case is not 100% safe for all initializers I had expected it to work for testing and was surprised when it did not.</p>
<p>I've tested and confirmed this behavior back to 2.5 and tested no further back</p> Ruby master - Bug #17595 (Rejected): [Pattern Matching] deconstruct_keys with zero patterns suppl...https://bugs.ruby-lang.org/issues/175952021-01-30T06:10:36Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>Example:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Test</span> <span class="o">=</span> <span class="no">Struct</span><span class="p">.</span><span class="nf">new</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="k">do</span>
<span class="k">def</span> <span class="nf">deconstruct_keys</span><span class="p">(</span><span class="n">ks</span><span class="p">)</span>
<span class="nb">p</span> <span class="ss">ks: </span><span class="n">ks</span>
<span class="n">to_h</span><span class="p">.</span><span class="nf">slice</span><span class="p">(</span><span class="o">*</span><span class="n">ks</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># => Test</span>
<span class="no">Test</span><span class="p">.</span><span class="nf">new</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="k">in</span> <span class="p">{}</span>
<span class="c1"># {:ks=>nil}</span>
<span class="c1"># => true</span>
</code></pre>
<p>As users may call <code>Array</code> methods against the <code>keys</code> argument I consider this a potential bug that it returns <code>nil</code> for no supplied arguments.</p>
<p><strong>Proposed Patch</strong>: Pass an empty array instead to maintain interface consistency.</p> Ruby master - Feature #17551 (Closed): Pattern Matching - Default Object#deconstruct and Object#d...https://bugs.ruby-lang.org/issues/175512021-01-18T04:25:24Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>Pattern Matching is a very powerful feature, but many classes are not able to enjoy its functionality due to the lacking of a default <code>deconstruct</code> and <code>deconstruct_keys</code> method being present.</p>
<p>This feature request is to introduce a series of sane defaults, and what they might look like. What these defaults are or should be is up for debate, but I would like to propose a few ideas to get things started.</p>
<a name="Reasonable-Defaults"></a>
<h2 >Reasonable Defaults<a href="#Reasonable-Defaults" class="wiki-anchor">¶</a></h2>
<a name="The-Fast-Version"></a>
<h3 >The Fast Version<a href="#The-Fast-Version" class="wiki-anchor">¶</a></h3>
<p>I will elaborate on this in the below content, but the fast version of my proposal is:</p>
<ol>
<li>
<code>deconstruct_keys</code> should default to a classes public API</li>
<li>
<code>deconstruct</code> should default to being an alias of <code>to_a</code> or other <code>Array</code> coercion methods</li>
</ol>
<a name="Deconstruct-Keys"></a>
<h3 >Deconstruct Keys<a href="#Deconstruct-Keys" class="wiki-anchor">¶</a></h3>
<p><code>deconstruct_keys</code> is used for extracting values out of an object in use with a <code>Hash</code>-like pattern match. In the case of a literal <code>Hash</code> with <code>Symbol</code> keys the deconstructed keys are extracted from the <code>Hash</code>.</p>
<p>My proposal would be to base the default <code>deconstruct_keys</code> on the attributes of an object as defined by <code>attr_*</code> methods. Consider this <code>Person</code> class:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Person</span>
<span class="nb">attr_reader</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:age</span><span class="p">,</span> <span class="ss">:children</span>
<span class="k">def</span> <span class="nf">initialize</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="ss">children: </span><span class="p">[])</span>
<span class="vi">@name</span> <span class="o">=</span> <span class="nb">name</span>
<span class="vi">@age</span> <span class="o">=</span> <span class="n">age</span>
<span class="vi">@children</span> <span class="o">=</span> <span class="n">children</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>The attributes exposed by the proposed default <code>deconstruct_keys</code> would be <code>name</code>, <code>age</code>, and <code>children</code>.</p>
<p>As <code>attr_reader</code> has made these values public they are the interface into the class, meaning this will not break encapsulation of values and relies on the already established API it provides.</p>
<p>In current Ruby this behavior can be approximated as seen here in a test gem I call Dio: <a href="https://github.com/baweaver/dio#attribute-forwarder" class="external">https://github.com/baweaver/dio#attribute-forwarder</a></p>
<p>It does a comparison of instance variables versus all methods to find public readers:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">ivars</span> <span class="o">=</span> <span class="no">Set</span><span class="p">.</span><span class="nf">new</span> <span class="n">base_object</span>
<span class="p">.</span><span class="nf">instance_variables</span>
<span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="n">_1</span><span class="p">.</span><span class="nf">to_s</span><span class="p">.</span><span class="nf">delete</span><span class="p">(</span><span class="s1">'@'</span><span class="p">).</span><span class="nf">to_sym</span> <span class="p">}</span>
<span class="n">all_methods</span> <span class="o">=</span> <span class="no">Set</span><span class="p">.</span><span class="nf">new</span> <span class="n">base_object</span><span class="p">.</span><span class="nf">methods</span>
<span class="n">attributes</span> <span class="o">=</span> <span class="n">ivars</span><span class="p">.</span><span class="nf">intersection</span><span class="p">(</span><span class="n">all_methods</span><span class="p">)</span>
</code></pre>
<p>Which allows me to do this:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="ss">name: </span><span class="s1">'Alice'</span><span class="p">,</span>
<span class="ss">age: </span><span class="mi">40</span><span class="p">,</span>
<span class="ss">children: </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="ss">name: </span><span class="s1">'Jim'</span><span class="p">,</span> <span class="ss">age: </span><span class="mi">10</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="ss">name: </span><span class="s1">'Jill'</span><span class="p">,</span> <span class="ss">age: </span><span class="mi">10</span><span class="p">)</span>
<span class="p">]</span>
<span class="p">)</span>
<span class="k">case</span> <span class="no">Dio</span><span class="p">.</span><span class="nf">attribute</span><span class="p">(</span><span class="n">alice</span><span class="p">)</span>
<span class="k">in</span> <span class="p">{</span> <span class="ss">name: </span><span class="sr">/^A/</span><span class="p">,</span> <span class="ss">age: </span><span class="mi">30</span><span class="o">..</span><span class="mi">50</span> <span class="p">}</span>
<span class="kp">true</span>
<span class="k">else</span>
<span class="kp">false</span>
<span class="k">end</span>
<span class="k">case</span> <span class="no">Dio</span><span class="p">.</span><span class="nf">attribute</span><span class="p">(</span><span class="n">alice</span><span class="p">)</span>
<span class="k">in</span> <span class="p">{</span> <span class="ss">children: </span><span class="p">[</span><span class="o">*</span><span class="p">,</span> <span class="p">{</span> <span class="ss">name: </span><span class="sr">/^J/</span> <span class="p">},</span> <span class="o">*</span><span class="p">]</span> <span class="p">}</span>
<span class="kp">true</span>
<span class="k">else</span>
<span class="kp">false</span>
<span class="k">end</span>
</code></pre>
<p>My list of ideas for this default <code>deconstruct_keys</code> method are:</p>
<ol>
<li>
<code>attr_</code> based - Any exposed attribute</li>
<li>public method based (<code>public_send</code>) - All public methods on the class</li>
<li>all methods (<code>send</code>) - Every potential method</li>
</ol>
<p>I believe the first is the most conservative and Ruby-like, as well as the least surprising. A case could be made for the second which allows for more flexibility and remains within the encapsulation of the class. The third is more unrealistic as it exposes everything.</p>
<p>I would like to discuss between the first two.</p>
<a name="Deconstruct"></a>
<h3 >Deconstruct<a href="#Deconstruct" class="wiki-anchor">¶</a></h3>
<p><code>deconstruct</code> is used for extracting values out of an object in use with an <code>Array</code>-like pattern match. In the case of an <code>Array</code> the values are returned directly.</p>
<p>My proposal would be to base the default <code>deconstruct</code> on the Ruby concept of Duck typing through <code>to_a</code> or <code>Enumerable</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">module</span> <span class="nn">Enumerable</span>
<span class="kp">alias_method</span> <span class="ss">:deconstruct</span><span class="p">,</span> <span class="ss">:to_a</span>
<span class="k">end</span>
</code></pre>
<p>Consider this <code>Node</code> class:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Node</span>
<span class="nb">attr_reader</span> <span class="ss">:value</span><span class="p">,</span> <span class="ss">:children</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="o">*</span><span class="n">children</span><span class="p">)</span>
<span class="vi">@value</span> <span class="o">=</span> <span class="n">value</span>
<span class="vi">@children</span> <span class="o">=</span> <span class="n">children</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">to_a</span><span class="p">()</span> <span class="o">=</span> <span class="p">[</span><span class="vi">@value</span><span class="p">,</span> <span class="vi">@children</span><span class="p">]</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">[]</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="o">=</span> <span class="n">new</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="k">end</span>
</code></pre>
<p>It is <code>Array</code>-like in nature, and through <code>to_a</code> we could infer <code>deconstruct</code> instead of explicitly requiring a method:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">tree</span> <span class="o">=</span> <span class="no">Node</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span>
<span class="no">Node</span><span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="no">Node</span><span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="no">Node</span><span class="p">[</span><span class="mi">4</span><span class="p">]]],</span>
<span class="no">Node</span><span class="p">[</span><span class="mi">5</span><span class="p">],</span>
<span class="no">Node</span><span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="no">Node</span><span class="p">[</span><span class="mi">7</span><span class="p">],</span> <span class="no">Node</span><span class="p">[</span><span class="mi">8</span><span class="p">]]</span>
<span class="p">]</span>
<span class="k">case</span> <span class="n">tree</span>
<span class="k">in</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="p">[</span><span class="o">*</span><span class="p">,</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="n">_</span><span class="p">],</span> <span class="o">*</span><span class="p">]]</span>
<span class="kp">true</span>
<span class="k">else</span>
<span class="kp">false</span>
<span class="k">end</span>
</code></pre>
<p>I believe this is a good use of duck typing, and presents a reasonable default. If no <code>Array</code> coercion methods are available it would make sense that it cannot be pattern matched against like an Array.</p>
<p>My proposal here is to use the established <code>to_a</code> or other <code>Array</code> coercion methods to imply <code>deconstruct</code></p>
<a name="Why-Defaults"></a>
<h2 >Why Defaults?<a href="#Why-Defaults" class="wiki-anchor">¶</a></h2>
<p>Many Ruby gems and code do not implement <code>deconstruct</code> or <code>deconstruct_keys</code>, meaning pattern matching cannot be used against them easily. This change will allow for pattern matching against Ruby code from any generation, and open up the feature to far more use across code bases.</p>
<p>I believe this feature would not be substantial work to implement, but will have substantial gains for all Ruby code.</p>
<p>Thank you for your time in reading, and I apologize for another long feature request.</p> Ruby master - Feature #17292 (Closed): Hash Shorthand / Punninghttps://bugs.ruby-lang.org/issues/172922020-10-29T05:07:18Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<a name="Set-Literal-vs-Javascript-Object-Punning"></a>
<h3 >Set Literal vs Javascript Object Punning<a href="#Set-Literal-vs-Javascript-Object-Punning" class="wiki-anchor">¶</a></h3>
<p>There was a proposal for a Set literal here: <a href="https://bugs.ruby-lang.org/issues/16989" class="external">https://bugs.ruby-lang.org/issues/16989</a></p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">set</span> <span class="o">=</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>
</code></pre>
<p>...but it was brought up that this is similar to the Javascript Object punning, or Object shorthand syntax:</p>
<pre><code class="js syntaxhl" data-language="js"><span class="kd">const</span> <span class="nx">a</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">b</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="nx">c</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">punnedObject</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">,</span> <span class="nx">c</span> <span class="p">}</span>
<span class="c1">// => { a: 1, b: 2, c: 3 }</span>
</code></pre>
<p><strong>Proposition</strong>: I believe we should use brackets (<code>{}</code>) for a shorthand Hash syntax similar to Javascript.</p>
<a name="Hash-Punning"></a>
<h3 >Hash Punning<a href="#Hash-Punning" class="wiki-anchor">¶</a></h3>
<p>My first proposal in this feature request is Hash punning, or Hash shorthand:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">a</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">b</span> <span class="o">=</span> <span class="mi">2</span>
<span class="n">c</span> <span class="o">=</span> <span class="mi">3</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="ss">c: </span><span class="p">}</span>
<span class="c1"># => { a: 1, b: 2, c: 3 }</span>
</code></pre>
<p>This syntax avoids the ambiguous syntax of empty block (<code>{}</code>) versus empty set (<code>{}</code>), and with the presence of Symbols it introduces a distinct syntax that would be easier to parse against.</p>
<p>One potential issue would be mixed syntax:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="p">{</span> <span class="n">a</span><span class="p">:,</span> <span class="ss">b: </span><span class="mi">2</span> <span class="p">}</span>
<span class="c1"># => { a: 1, b: 2 }</span>
</code></pre>
<a name="Method-Punning"></a>
<h3 >Method Punning<a href="#Method-Punning" class="wiki-anchor">¶</a></h3>
<p>This syntax can also be used for keyword argument and method call punning:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">method_name</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="n">a</span> <span class="o">+</span> <span class="n">b</span> <span class="o">+</span> <span class="n">c</span>
<span class="k">end</span>
<span class="n">a</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">b</span> <span class="o">=</span> <span class="mi">2</span>
<span class="n">c</span> <span class="o">=</span> <span class="mi">3</span>
<span class="n">method_name</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="c1"># => 6</span>
</code></pre>
<p>I believe this existing syntax for required keywords gives credence to the idea of introducing punning to Ruby, as it's very similar to existing syntax, and therefor feels "Ruby-like".</p>
<a name="Pattern-Matching"></a>
<h3 >Pattern Matching<a href="#Pattern-Matching" class="wiki-anchor">¶</a></h3>
<p>This syntax is also already present and used in pattern matching, making it already part of the language:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">case</span> <span class="p">{</span> <span class="ss">x: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">y: </span><span class="mi">2</span> <span class="p">}</span>
<span class="k">in</span> <span class="p">{</span> <span class="n">x</span><span class="p">:,</span> <span class="ss">y: </span><span class="p">}</span>
<span class="p">{</span> <span class="n">x</span><span class="p">:,</span> <span class="ss">y: </span><span class="n">y</span> <span class="o">+</span> <span class="mi">1</span><span class="p">}</span> <span class="c1"># new</span>
<span class="k">else</span>
<span class="c1"># ...</span>
<span class="k">end</span>
</code></pre>
<p>I believe this further justifies the case for punning syntax.</p> Ruby master - Feature #17140 (Open): Merge Enumerable#grep(_v) with Enumerable#select/rejecthttps://bugs.ruby-lang.org/issues/171402020-09-01T19:52:55Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>In recent versions of Ruby we've gotten new behavior of some Enumerable methods like any?, all?, none?, one?, and others to support a single argument pattern that responds to <code>===</code>. This is very powerful, and very useful.</p>
<p>Currently Enumerable has <code>grep</code> and <code>grep_v</code> which allow this as a way to filter lists.</p>
<p>These names require some understanding of Unix to be familiar with, but naming aside, I feel it may make sense to implement <code>===</code> pattern arguments in <code>Enumerable#select</code> and <code>Enumerable#reject</code> as with the above.</p>
<p>Proposed Syntax:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">list_of_numbers</span><span class="p">.</span><span class="nf">select</span><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="p">)</span>
<span class="n">words</span><span class="p">.</span><span class="nf">reject</span><span class="p">({</span> <span class="s1">'and'</span><span class="p">,</span> <span class="s1">'the'</span><span class="p">,</span> <span class="s1">'of'</span> <span class="p">})</span>
</code></pre>
<p>I believe this would help with readability and would simplify syntax options by unifying on this standard.</p>
<p>My concern is that <code>Enumerable#find</code> already takes a single argument, <code>ifnone</code>, and may not be able to implement this behavior. I would be curious to see how many use <code>ifnone</code> but feel this would be more critically breaking to do.</p> Ruby master - Misc #15568 (Open): TracePoint(:raise)#parameters raises RuntimeErrorhttps://bugs.ruby-lang.org/issues/155682019-01-26T21:10:29Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>Currently trying to get the <code>trace.parameters</code> of a method in a <code>raise</code> event will lead to a RuntimeError. I would contend that it should not, and that it would be perfectly valid to ask for the parameters in the case of an exception.</p>
<p>The reason I do this is to see the arguments at the time of exception:</p>
<pre><code>def extract_args(trace)
trace.parameters.map(&:last).to_h do |name|
[name, trace.binding.eval(name.to_s)]
end
end
</code></pre>
<p>I've noticed that I can technically "cheat" and get these same values like this:</p>
<pre><code>def extract_args(trace)
trace.binding.eval('local_variables').to_h do |name|
[name, trace.binding.eval(name.to_s)]
end
end
</code></pre>
<p>Having the ability to get the parameters in a <code>raise</code> context would be very useful for debugging.</p>
<p>I'm tempted to also suggest <code>TracePoint#local_variables</code> as it would provide additional context in a more exposed way than <code>TracePoint#binding.eval('local_variables')</code></p> Ruby master - Feature #15559 (Open): Logical XOR (^^) operatorhttps://bugs.ruby-lang.org/issues/155592019-01-23T17:17:23Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>Currently we have bitwise <code>&</code> and logical <code>&&</code>, and bitwise <code>|</code> and logical <code>||</code>. Would it be possible to have logical <code>^^</code> in addition to the bitwise <code>^</code> for XOR?</p> Ruby master - Feature #15541 (Third Party's Issue): Add alias symbolize_keys for symbolize_names ...https://bugs.ruby-lang.org/issues/155412019-01-16T01:44:56Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p><a href="https://github.com/ruby/psych/issues/341" class="external">https://github.com/ruby/psych/issues/341</a></p>
<p>When trying to symbolize keys on JSON parsing, it's really hard to remember the name <code>symbolize_names</code>:</p>
<pre><code>JSON.parse(data, symbolize_names: true)
</code></pre>
<p>I would like to propose that we change this keyword to <code>symbolize_keys</code> to be more clear:</p>
<pre><code>JSON.parse(data, symbolize_keys: true)
</code></pre>
<p>The documentation for this method also reflects the confusion: <a href="http://ruby-doc.org/stdlib-2.6/libdoc/json/rdoc/JSON.html#method-i-parse-21" class="external">http://ruby-doc.org/stdlib-2.6/libdoc/json/rdoc/JSON.html#method-i-parse-21</a></p>
<pre><code>symbolize_names: If set to true, returns symbols for the names (keys) in a JSON object. Otherwise strings are returned. Strings are the default.
</code></pre>
<p>The same issue came up in Psych not too long ago:</p>
<p><a href="https://github.com/ruby/psych/issues/341" class="external">https://github.com/ruby/psych/issues/341</a></p>
<p>I believe the current name causes confusion. Would it be possible to add an alias to this keyword for clarity?</p> Ruby master - Feature #14967 (Open): Any typehttps://bugs.ruby-lang.org/issues/149672018-08-06T03:09:33Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>In Scala, there's the concept of an Any type which can be used to match anything.</p>
<p>The implementation of which is quite simple: <a href="https://github.com/baweaver/any" class="external">https://github.com/baweaver/any</a></p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Any</span>
<span class="k">class</span> <span class="o"><<</span> <span class="nb">self</span>
<span class="k">def</span> <span class="nf">===</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>
<span class="kp">true</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">==</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>
<span class="kp">true</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">to_proc</span>
<span class="nb">proc</span> <span class="p">{</span> <span class="kp">true</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>What this allows us though is the ability to really maximize the potentials of both <code>Hash#===</code> [Feature <a class="issue tracker-2 status-1 priority-4 priority-default" title="Feature: Proposal to add Hash#=== (Open)" href="https://bugs.ruby-lang.org/issues/14869">#14869</a>] and <code>Array#===</code> [Feature <a class="issue tracker-2 status-1 priority-4 priority-default" title="Feature: Proposal to add Array#=== (Open)" href="https://bugs.ruby-lang.org/issues/14916">#14916</a>]:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">case</span> <span class="p">[</span><span class="s1">'Foo'</span><span class="p">,</span> <span class="mi">25</span><span class="p">]</span>
<span class="k">when</span> <span class="p">[</span><span class="sr">/^F/</span><span class="p">,</span> <span class="no">Any</span><span class="p">]</span> <span class="k">then</span> <span class="kp">true</span>
<span class="k">else</span> <span class="kp">false</span>
<span class="k">end</span>
<span class="c1"># => true</span>
<span class="k">case</span> <span class="p">{</span><span class="ss">id: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">name: </span><span class="s1">'foo'</span><span class="p">,</span> <span class="ss">age: </span><span class="mi">42</span><span class="p">}</span>
<span class="k">when</span> <span class="p">{</span><span class="ss">id: </span><span class="no">Any</span><span class="p">,</span> <span class="ss">name: </span><span class="sr">/^f/</span><span class="p">,</span> <span class="ss">age: </span><span class="no">Any</span><span class="p">}</span> <span class="k">then</span> <span class="kp">true</span>
<span class="k">else</span> <span class="kp">false</span>
<span class="k">end</span>
<span class="c1"># => true</span>
<span class="k">case</span> <span class="p">{</span><span class="ss">id: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">name: </span><span class="s1">'foo'</span><span class="p">}</span>
<span class="k">when</span> <span class="p">{</span><span class="ss">id: </span><span class="no">Any</span><span class="p">,</span> <span class="ss">name: </span><span class="sr">/^f/</span><span class="p">,</span> <span class="ss">age: </span><span class="no">Any</span><span class="p">}</span> <span class="k">then</span> <span class="kp">true</span>
<span class="k">else</span> <span class="kp">false</span>
<span class="k">end</span>
<span class="c1"># => false</span>
</code></pre>
<p>This could potentially be an alias for Object as well, as the current idea would only work with <code>===</code>. <code>is_a?</code> would return false.</p>
<p>If we choose to pursue pattern matching [Feature <a class="issue tracker-2 status-5 priority-4 priority-default closed" title="Feature: Introduce pattern matching syntax (Closed)" href="https://bugs.ruby-lang.org/issues/14912">#14912</a>] further, I believe a wildcard type would be exceptionally useful.</p> Ruby master - Bug #14695 (Closed): [2.5.1] `===` is 1.77x slower than `match?`https://bugs.ruby-lang.org/issues/146952018-04-18T07:30:38Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>Was evaluating some of the <code>===</code> implementations while testing and came across this one:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]</span>
<span class="nb">require</span> <span class="s1">'benchmark/ips'</span>
<span class="k">def</span> <span class="nf">run_benchmark</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="o">**</span><span class="n">benchmarks</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s1">''</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="s1">'='</span> <span class="o">*</span> <span class="n">title</span><span class="p">.</span><span class="nf">size</span><span class="p">,</span> <span class="s1">''</span>
<span class="c1"># Validation</span>
<span class="n">benchmarks</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">benchmark_name</span><span class="p">,</span> <span class="n">benchmark_fn</span><span class="o">|</span>
<span class="nb">puts</span> <span class="s2">"</span><span class="si">#{</span><span class="n">benchmark_name</span><span class="si">}</span><span class="s2"> result: </span><span class="si">#{</span><span class="n">benchmark_fn</span><span class="p">.</span><span class="nf">call</span><span class="p">()</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="nb">puts</span>
<span class="no">Benchmark</span><span class="p">.</span><span class="nf">ips</span> <span class="k">do</span> <span class="o">|</span><span class="n">bm</span><span class="o">|</span>
<span class="n">benchmarks</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">benchmark_name</span><span class="p">,</span> <span class="n">benchmark_fn</span><span class="o">|</span>
<span class="n">bm</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="n">benchmark_name</span><span class="p">,</span> <span class="o">&</span><span class="n">benchmark_fn</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">bm</span><span class="p">.</span><span class="nf">compare!</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">regex</span> <span class="o">=</span> <span class="sr">/foo/</span>
<span class="c1"># => /foo/</span>
<span class="n">string</span> <span class="o">=</span> <span class="s1">'foobarbaz'</span>
<span class="c1"># => "foobarbaz"</span>
<span class="n">run_benchmark</span><span class="p">(</span><span class="s1">'=== vs match? - 2.5.1'</span><span class="p">,</span>
<span class="s1">'==='</span><span class="p">:</span> <span class="o">-></span> <span class="p">{</span> <span class="n">regex</span> <span class="o">===</span> <span class="n">string</span> <span class="p">},</span>
<span class="s1">'match?'</span><span class="p">:</span> <span class="o">-></span> <span class="p">{</span> <span class="n">regex</span><span class="p">.</span><span class="nf">match?</span> <span class="n">string</span> <span class="p">}</span>
<span class="p">)</span>
<span class="o">===</span> <span class="n">vs</span> <span class="n">match?</span> <span class="o">-</span> <span class="mf">2.5</span><span class="o">.</span><span class="mi">1</span>
<span class="o">=====================</span>
<span class="o">===</span> <span class="ss">result: </span><span class="kp">true</span>
<span class="n">match?</span> <span class="ss">result: </span><span class="kp">true</span>
<span class="no">Warming</span> <span class="n">up</span> <span class="o">--------------------------------------</span>
<span class="o">===</span> <span class="mf">173.435</span><span class="n">k</span> <span class="n">i</span><span class="o">/</span><span class="mi">100</span><span class="n">ms</span>
<span class="n">match?</span> <span class="mf">233.124</span><span class="n">k</span> <span class="n">i</span><span class="o">/</span><span class="mi">100</span><span class="n">ms</span>
<span class="no">Calculating</span> <span class="o">-------------------------------------</span>
<span class="o">===</span> <span class="mf">3.174</span><span class="no">M</span> <span class="p">(</span><span class="err">±</span> <span class="mf">1.6</span><span class="o">%</span><span class="p">)</span> <span class="n">i</span><span class="o">/</span><span class="n">s</span> <span class="o">-</span> <span class="mf">15.956</span><span class="no">M</span> <span class="k">in</span> <span class="mf">5.029027</span><span class="n">s</span>
<span class="n">match?</span> <span class="mf">5.626</span><span class="no">M</span> <span class="p">(</span><span class="err">±</span> <span class="mf">2.5</span><span class="o">%</span><span class="p">)</span> <span class="n">i</span><span class="o">/</span><span class="n">s</span> <span class="o">-</span> <span class="mf">28.208</span><span class="no">M</span> <span class="k">in</span> <span class="mf">5.016991</span><span class="n">s</span>
<span class="no">Comparison</span><span class="p">:</span>
<span class="ss">match?: </span><span class="mf">5626170.1</span> <span class="n">i</span><span class="o">/</span><span class="n">s</span>
<span class="o">===</span><span class="p">:</span> <span class="mf">3173659.6</span> <span class="n">i</span><span class="o">/</span><span class="n">s</span> <span class="o">-</span> <span class="mf">1.77</span><span class="n">x</span> <span class="n">slower</span>
</code></pre>
<p>It appears that <code>===</code> is running a bit slower than <code>match?</code>, though there may be some concerns around old code potentially relying on regex set globals after <code>===</code> too in case statements, so not sure there.</p> Ruby master - Bug #12294 (Rejected): String encoding methods renamehttps://bugs.ruby-lang.org/issues/122942016-04-17T00:07:34Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p><a href="http://ruby-doc.org/stdlib-2.3.0/libdoc/nkf/rdoc/String.html" class="external">http://ruby-doc.org/stdlib-2.3.0/libdoc/nkf/rdoc/String.html</a></p>
<p>Most of the string encoding methods are named counter to the established pattern of other check and to_ methods.</p>
<p>Proposed changes:</p>
<pre><code>iseuc -> euc?
isjis -> jis?
issjis -> sjis?
toeuc -> to_euc
tojus -> to_jis
tolocale -> to_locale
tosjis -> to_sjis
toutf16 -> to_utf16
toutf32 -> to_utf32
toutf8 -> to_utf8
</code></pre> Ruby master - Feature #10308 (Open): Pipes in Rubyhttps://bugs.ruby-lang.org/issues/103082014-09-30T22:06:38Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<p>Much akin to the Unix and Elixir piping, I think it would be an interesting feature to consider in Ruby.</p>
<p>There have already been a number of hack implementations of it:<br>
<a href="https://gist.github.com/pcreux/2f87847e5e4aad37db02" class="external">https://gist.github.com/pcreux/2f87847e5e4aad37db02</a><br>
<a href="https://github.com/banister/funkify" class="external">https://github.com/banister/funkify</a><br>
<a href="https://github.com/baweaver/streamable" class="external">https://github.com/baweaver/streamable</a></p>