https://redmine.ruby-lang.org/https://redmine.ruby-lang.org/favicon.ico?17113305112018-09-05T16:22:56ZRuby Issue Tracking SystemRuby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=739102018-09-05T16:22:56Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<ul><li><strong>Related to</strong> <i><a class="issue tracker-1 status-5 priority-4 priority-default closed" href="/issues/10856">Bug #10856</a>: Splat with empty keyword args gives unexpected results</i> added</li></ul> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=739182018-09-05T23:02:58Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul></ul><p>It is not so trivial what it should be. If you look at only <code>foo(**{}) #=> [{}]</code> itself, it might be non-intuitive, indeed. However, IMO, this is a consequence of the current weird spec of keyword arguments (a keyword hash is passed as a normal last argument). Unless the spec itself changes, it would be more consistent for <code>foo(**{})</code> to return <code>[{}]</code>.</p>
<p>See the following example:</p>
<pre><code>def foo(*args)
p args
end
foo(**{k1: 1, k2: 2, k3: 3}) #=> [{:k1=>1, :k2=>2, :k3=>3}]
foo(**{k1: 1, k2: 2}) #=> [{:k1=>1, :k2=>2}]
foo(**{k1: 1}) #=> [{:k1=>1}]
foo(**{}) #=> [] # surprising, should be [{}]
</code></pre>
<p>The number of arguments changes depending upon the value. I think such a behavior is error-prone. It should not occur, unless the caller uses <code>foo(*ary)</code> explicitly. People will not expect the number change in <code>foo(**hsh)</code>.</p>
<p>In fact, the fix you proposed will make it difficult to test the following program:</p>
<pre><code>def f2(h)
end
def f1(foo, bar)
h = {}
h[:foo] = :foo if foo
h[:bar] = :bar if bar
f2(**h)
end
</code></pre>
<p>It will break only when <code>f1(false, false)</code>.</p>
<p>Do you think it is bad to accept a keyword hash (<code>f2(**h)</code>) as a normal parameter (<code>def f2(h)</code>)? Yes, I agree. So I'm proposing <a class="issue tracker-2 status-5 priority-4 priority-default closed" title="Feature: "Real" keyword argument (Closed)" href="https://redmine.ruby-lang.org/issues/14183">#14183</a>. If keyword arguments are split from normal arguments, we can fix this issue gracefully: we can always ignore <code>**{}</code> safely. If the current weird spec is kept, I don't think it is a good idea to "fix" this ad-hocly just because it might be non-intuitive.</p> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=739202018-09-05T23:03:32Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul><li><strong>Related to</strong> <i><a class="issue tracker-1 status-5 priority-4 priority-default closed" href="/issues/15052">Bug #15052</a>: must not optimize `foo(**{})` out</i> added</li></ul> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=739222018-09-05T23:03:36Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul><li><strong>Related to</strong> <i><a class="issue tracker-2 status-5 priority-4 priority-default closed" href="/issues/14183">Feature #14183</a>: "Real" keyword argument</i> added</li></ul> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=739242018-09-06T03:51:11Zsawa (Tsuyoshi Sawada)
<ul></ul><p>It is also related to<br>
<a href="https://bugs.ruby-lang.org/issues/11860" class="external">https://bugs.ruby-lang.org/issues/11860</a><br>
<a href="https://bugs.ruby-lang.org/issues/12022" class="external">https://bugs.ruby-lang.org/issues/12022</a><br>
<a href="https://bugs.ruby-lang.org/issues/10708" class="external">https://bugs.ruby-lang.org/issues/10708</a></p> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=739302018-09-06T18:50:00Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<ul></ul><p>First, I hope we can agree that <code>any(**{})</code> and <code>any(**Hash.new)</code> should have the exact same result (for any definition of <code>any</code>).</p>
<p>So the question is: what should the result be?</p>
<p>For me, writing <code>**hash</code> means "do as if I had written that hash inline".<br>
I.e. <code>any(**{k1: 1, k2: 2})</code> should have the exact same result as <code>any(k1: 1, k2: 2)</code><br>
In the same way, <code>any(**{})</code> should have the exact same result as <code>any()</code></p>
<p>Advantages I see:<br>
a) Allows two ways to safely forward all arguments to another method:</p>
<pre><code>def middle(*args, &b)
forward_to(*args, &b)
end
# or equivalently
def middle(*args, **options, &b)
forward_to(*args, **options, &b)
end
</code></pre>
<p>In either cases, calling <code>middle(...)</code> has same effect as <code>forward_to(...)</code>.</p>
<p>My guess is that a huge number of <code>method_missing</code> definitions (like that of <code>lib/delegate</code>: <a href="https://github.com/ruby/ruby/blob/trunk/lib/delegate.rb#L78-L89" class="external">https://github.com/ruby/ruby/blob/trunk/lib/delegate.rb#L78-L89</a>) use either formulas to forward calls, for example.</p>
<p>b) Compatible with current behavior for most methods (those without positional rest argument)</p>
<pre><code>empty_hash = {}
def foo(a, b); end; foo(1, 2, **empty_hash) # => no error
def foo(a, b=nil); end; foo(1, 2, **empty_hash) # => no error
</code></pre>
<p>Of course, one wouldn't write such code, but this is related to the previous point.</p>
<p>mame (Yusuke Endoh) wrote:</p>
<blockquote>
<p>Do you think it is bad to accept a keyword hash (f2(**h)) as a normal parameter (def f2(h))? Yes, I agree.</p>
</blockquote>
<p>I am convinced that the vast majority of rubyists would either write <code>def f2(options = {})</code> (old style), or <code>def f2(**options)</code> (new style). I think it is good to accept <code>f2(**h)</code> as a normal parameter <code>def f2(h = {})</code>.</p>
<blockquote>
<p>So I'm proposing <a class="issue tracker-2 status-5 priority-4 priority-default closed" title="Feature: "Real" keyword argument (Closed)" href="https://redmine.ruby-lang.org/issues/14183">#14183</a>.</p>
</blockquote>
<p>I understand the goal and I completely agree that if we were to re-create Ruby from scratch it would be great. But Ruby is more than 20 years old with over 100k gems. Because of that my position has to be strongly against the proposal.</p>
<p>Now even if the proposal was accepted and implemented, then <code>**{}</code> would still never create a positional argument! So I believe that you actually agree with me :-)</p> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=739352018-09-07T16:36:38Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul></ul><p>marcandre (Marc-Andre Lafortune) wrote:</p>
<blockquote>
<p>First, I hope we can agree that <code>any(**{})</code> and <code>any(**Hash.new)</code> should have the exact same result (for any definition of <code>any</code>).</p>
</blockquote>
<p>Of course :-)</p>
<blockquote>
<p>For me, writing <code>**hash</code> means "do as if I had written that hash inline".<br>
I.e. <code>any(**{k1: 1, k2: 2})</code> should have the exact same result as <code>any(k1: 1, k2: 2)</code><br>
In the same way, <code>any(**{})</code> should have the exact same result as <code>any()</code></p>
</blockquote>
<p>What do you think about <a class="issue tracker-1 status-5 priority-4 priority-default closed" title="Bug: must not optimize `foo(**{})` out (Closed)" href="https://redmine.ruby-lang.org/issues/15052">#15052</a>?</p>
<pre><code>def foo(opt = "opt", **hsh)
p [opt, hsh]
end
foo({}, **{}) #=> expected: [{}, {}], actual: ["opt", {}]
</code></pre>
<p>According to your interpretation, <code>foo({}, **{})</code> is equal to <code>foo({})</code>, so the current result is correct. We need to write <code>foo({}, {})</code> to get the expected result.<br>
I added <code>**</code> explicitly to make sure that the second hash is a keyword hash, which made a bug. This spoils the feature of double splat, I think.</p>
<blockquote>
<p>Advantages I see:<br>
a) Allows two ways to safely forward all arguments to another method:</p>
</blockquote>
<p>Yes, good point. If keyword arguments are separated from normal ones, we need to always forward three component: <code>def foo(*a, **h, &b); bar(*a, **h, &b); end</code>. However, the code does not always work on the current semantics, as you said. We need a migration path, or a coding style that works on both 2.X and 3.X, so this is a severe problem.</p>
<p>Surprisingly, in 2010, matz predicted this issue and proposed a new syntax for delegation in <a class="issue tracker-2 status-5 priority-4 priority-default closed" title="Feature: argument delegation (Closed)" href="https://redmine.ruby-lang.org/issues/3447">#3447</a>: <code>def foo(...); bar(...); end</code>. Unfortunately, it has not been accepted yet, though.</p>
<blockquote>
<p>Now even if the proposal was accepted and implemented, then <code>**{}</code> would still never create a positional argument! So I believe that you actually agree with me :-)</p>
</blockquote>
<p>I'd like to agree with you, but also really like to fix <a class="issue tracker-1 status-5 priority-4 priority-default closed" title="Bug: must not optimize `foo(**{})` out (Closed)" href="https://redmine.ruby-lang.org/issues/15052">#15052</a>. Do you find a good semantics of <code>**hash</code> to satisfy both this ticket and that one?</p> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=739452018-09-09T03:27:34Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<ul><li><strong>Assignee</strong> changed from <i>nobu (Nobuyoshi Nakada)</i> to <i>matz (Yukihiro Matsumoto)</i></li></ul><p>mame (Yusuke Endoh) wrote:</p>
<blockquote>
<p>I'd like to agree with you, but also really like to fix <a class="issue tracker-1 status-5 priority-4 priority-default closed" title="Bug: must not optimize `foo(**{})` out (Closed)" href="https://redmine.ruby-lang.org/issues/15052">#15052</a>. Do you find a good semantics of **hash to satisfy both this ticket and that one?</p>
</blockquote>
<p>This is a long post, sorry.</p>
<ul>
<li>Summary *</li>
</ul>
<p>I agree that in your "two hash" example, <code>foo({}, **{})</code> should return <code>[{}, {}]</code>. I believe it is possible to have semantics that allow that. I am proposing such a semantic below that would have the following consequences:</p>
<pre><code>h = {x: 42}
def with_keyword(*args, **options)
[args, options]
end
with_keyword(h, **{}) # => [[h], {}] (as in 15052, currently [[], h])
def no_keyword(*args)
args
end
no_keyword(h, **{}) # => [h] (currently: [h, {}])
</code></pre>
<p>The current behavior is actually problematic, in particular with full forwarding (of the type <code>*args, **options</code>), while I believe my proposal fixes it.<br>
This paves the way for a stricter handling of keyword parameters (like your proposal <a class="issue tracker-2 status-5 priority-4 priority-default closed" title="Feature: "Real" keyword argument (Closed)" href="https://redmine.ruby-lang.org/issues/14183">#14183</a>).</p>
<ul>
<li>Proposed semantics *</li>
</ul>
<p>Ruby 2.x will:</p>
<ol>
<li>promote the last positional argument as keyword argument whenever possible (as is currently does)</li>
<li>demote keywords arguments to a positional argument if needed (as is currently does, but maybe differently for empty case)</li>
</ol>
<p>In detail:</p>
<ol>
<li>When calling <code>method(a, b, last)</code>, then <code>last</code> will be promoted to a keyword argument if possible, i.e. if:<br>
a) <code>method</code> takes keyword arguments (any of <code>key:</code>, <code>key: val</code>, or <code>**options</code> in signature)<br>
b) and all mandatory positional arguments of <code>method</code> are provided (here by <code>a</code> and <code>b</code>)<br>
c) and <code>last</code> is hash-like (i.e. <code>responds_to? :to_hash</code>)<br>
d) and all keys of <code>last.to_hash</code> are symbols</li>
</ol>
<p>Otherwise, <code>last</code> will remain a positional argument.<br>
This is current behavior and should remain unchanged in Ruby 2.x, maybe open for removal in Ruby 3.</p>
<ol start="2">
<li>When calling <code>method(a, b, key: value)</code> or <code>method(a, b, **hash)</code> or a combination of these, the keyword arguments (here <code>{key: value}</code> or <code>hash</code>) will be demoted to a positional argument if needed, i.e.<br>
a) if <code>method</code> does not accept keyword arguments<br>
b) and they are non empty</li>
</ol>
<p>This is slightly different than currently in some cases for <code>**{}</code>.</p>
<ul>
<li>Limitation *</li>
</ul>
<p>While your example in "two hash" example would still work, it can't be forwarded with the naive approach:</p>
<pre><code>def with_keyword(*args, **options)
[args, options]
end
def forward(*args)
with_keyword(*args)
end
forward(h, **{}) # => [[], h] not same as with_keyword(h, **{}) # => [[h], {}]
</code></pre>
<p>Only forwarding with the full approach would work.</p>
<p>I believe that very few real life cases would be problematic. If your proposal is accepted, or half of it like I recommend, this won't be an issue.</p>
<ul>
<li>The future *</li>
</ul>
<p>I think the reason why we haven't had too many bug reports with <code>**{}</code> yet is that the usual generic ways of forwarding are either the <code>delegate</code> library or <code>ActiveSupport</code>'s <code>delegate :method, to: receiver</code>.</p>
<p>Both currently use the naive forwarding (<code>*args</code>) instead of full forwarding (<code>*args, **options</code>). So neither actually deal with <code>**{}</code>.</p>
<p>The naive forwarding relies on promotion of last positional argument to keyword arguments. Before we can remove that automatic promotion, we need to make sure that full forwarding works well.</p>
<p>If <code>**{}</code> creates a positional argument, this makes any normal method with optional arguments non compatible with full forwarding! For example:</p>
<pre><code>def multiply(*nbs)
nbs.inject(1, :*)
end
def forward(*a, **o)
multiply(*a, **o)
end
forward(1, 2, 3) # => should call multiply(1, 2, 3) but currently calls multiply(1, 2, 3, {}) which raises error.
</code></pre>
<p>I believe it is absolutely imperative that the code above works, in Ruby 2.x and 3.x.</p>
<p>For that reason I believe that <code>**{}</code> should never create a positional argument.</p> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=740352018-09-14T11:10:24Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul></ul><p>Marc-Andre,</p>
<p>marcandre (Marc-Andre Lafortune) wrote:</p>
<blockquote>
<ul>
<li>Proposed semantics *</li>
</ul>
</blockquote>
<p>I thought it looked good. However, I introduced it to some committers at the developers' meeting, and akr-san showed it was still incomplete:</p>
<pre><code>def target(*args)
p args
end
def forward(*args, **kw, &blk)
target(*args, **kw, &blk)
end
target(1, 2, 3, {}) #=> [1, 2, 3, {}]
forward(1, 2, 3, {}) #=> [1, 2, 3] on the semantics you proposed ([1, 2, 3, {}] on the current behavior)
</code></pre>
<p>Akr-san said that it would be impossible to create a "perfect" semantics to satisfy all cases. In Ruby 2.X, he said that <code>**empty_hash</code> should be just ignored, and that <code>foo({}, **{})</code> should be always equal to <code>foo({})</code>, even if <code>def foo(opt=42, **kw)</code> receives <code>opt=42</code>. (However, matz showed reluctance to this behavior. So, the solution for this ticket is not decided yet.)</p>
<p>Also, akr-san and some committers agreed with my proposal of "real" keyword arguments to solve all the kind of design flaws. (Surprisingly, there was no committer there who was against my proposal, even though it breaks compatibility. Matz also looked positive, but he has not made the decision yet.)<br>
It would be impossible to write a delegation code that works perfectly on both 2.X and 3.0, but it would work good enough on almost all practical cases, akr-san said.</p>
<p>Akr-san and some committers said that a hash or keyword should be decided syntactically based on <code>=></code> or <code>:</code>. <code>foo(key: 1) should pass a keyword and </code>foo(:key => 1)<code>should pass a hash (i.e.,</code>foo({:key => 1})`). I'll consider it and update my proposal.</p> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=740442018-09-14T18:04:11Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<ul></ul><p>mame (Yusuke Endoh) wrote:</p>
<blockquote>
<p>Akr-san said that it would be impossible to create a "perfect" semantics to satisfy all cases.</p>
</blockquote>
<p>I'm sorry if I sounded like I had a "complete" solution, since Akr-san is right, there is no such perfect semantics. Proof</p>
<pre><code># Let foo be some method of unknown signature
# Forward bar to foo:
def bar(*args, **opt, &b)
foo(*args, **opt, &b)
end
# If forwarding works, we have:
bar({}) === foo({})
bar() === foo()
# But by definition above, we have
bar({}) === foo(**{}) # Since args = [], opt = {} (at least in Ruby 2.x)
bar() === foo(**{}) # Since args = [], opt = {}
# So, for any `foo`, we would need
foo() === foo({}) # Not necessarily true...
</code></pre>
<blockquote>
<p>In Ruby 2.X, he said that <code>**empty_hash</code> should be just ignored, and that <code>foo({}, **{})</code> should be always equal to <code>foo({})</code>, even if <code>def foo(opt=42, **kw)</code> receives <code>opt=42</code>. (However, matz showed reluctance to this behavior. So, the solution for this ticket is not decided yet.)</p>
</blockquote>
<p>I'm glad he and I agree on the fact that is should be ignored most of the time. It could also not be ignored for methods that accept keyword parameters, which I believe is more logical and closer to what Ruby 3.0 would do.</p>
<blockquote>
<p>Also, akr-san and some committers agreed with my proposal of "real" keyword arguments to solve all the kind of design flaws. (Surprisingly, there was no committer there who was against my proposal, even though it breaks compatibility. Matz also looked positive, but he has not made the decision yet.)<br>
It would be impossible to write a delegation code that works perfectly on both 2.X and 3.0, but it would work good enough on almost all practical cases, akr-san said.</p>
</blockquote>
<p>Please consider my "vote" as in favor of half your proposal, and strong opposition to the other half :-)</p>
<blockquote>
<p>Akr-san and some committers said that a hash or keyword should be decided syntactically based on <code>=></code> or <code>:</code>.</p>
</blockquote>
<p>I agree that it should be syntactical. We can still decide that <code>foo(:key => 1)</code> is a keyword argument though. I'm undecided about this. Only important thing is that <code>foo(some_var => 1)</code> should never be considered a keyword argument, even if <code>some_var</code> is a Symbol.</p> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=740472018-09-15T01:08:19Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul></ul><p>marcandre (Marc-Andre Lafortune) wrote:</p>
<blockquote>
<p>I'm sorry if I sounded like I had a "complete" solution, since Akr-san is right, there is no such perfect semantics.</p>
</blockquote>
<p>Don't mind; I had considered your proposal in a few days, but I couldn't find the counterexample :-) The current semantics is just too complex.</p>
<blockquote>
<blockquote>
<p>In Ruby 2.X, he said that <code>**empty_hash</code> should be just ignored, and that <code>foo({}, **{})</code> should be always equal to <code>foo({})</code>, even if <code>def foo(opt=42, **kw)</code> receives <code>opt=42</code>. (However, matz showed reluctance to this behavior. So, the solution for this ticket is not decided yet.)</p>
</blockquote>
<p>I'm glad he and I agree on the fact that is should be ignored most of the time. It could also not be ignored for methods that accept keyword parameters, which I believe is more logical and closer to what Ruby 3.0 would do.</p>
</blockquote>
<p>Yes. Your proposal looks the best semantics so far for Ruby 2.X, at least to me. Note that akr-san is also reasonable: from 2.0, a method call has always passed the same value array and a block, without depending upon the definition of the method, he said. Your proposal breaks the principle. (Honestly, I'm unsure if the principle is still valid or not. Too complex...)</p>
<blockquote>
<p>Please consider my "vote" as in favor of half your proposal, and strong opposition to the other half :-)</p>
</blockquote>
<p>Did you see <a href="https://bugs.ruby-lang.org/issues/14183#note-29" class="external">my problem of the note 29 in #14183</a>? Unfortunately, prohibiting the half does not solve the problem at all.</p> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=740482018-09-15T02:33:45Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<ul></ul><p>mame (Yusuke Endoh) wrote:</p>
<blockquote>
<p>Yes. Your proposal looks the best semantics so far for Ruby 2.X, at least to me. Note that akr-san is also reasonable: from 2.0, a method call has always passed the same value array and a block, without depending upon the definition of the method, he said. Your proposal breaks the principle. (Honestly, I'm unsure if the principle is still valid or not. Too complex...)</p>
</blockquote>
<p>Indeed, my proposal breaks that (on purpose) :-)</p>
<blockquote>
<blockquote>
<p>Please consider my "vote" as in favor of half your proposal, and strong opposition to the other half :-)</p>
</blockquote>
<p>Did you see <a href="https://bugs.ruby-lang.org/issues/14183#note-29" class="external">my problem of the note 29 in #14183</a>? Unfortunately, prohibiting the half does not solve the problem at all.</p>
</blockquote>
<p>Yes, I saw it. It's definitely true my proposal wouldn't fix it (until Ruby 42.0.0 ;-) ). I'm not very concerned about it though. Care would still be required when changing a method from positional arguments only to keyword arguments, if the last positional argument could be a hash. That's a rare case, and you can write your code to avoid the issue.</p>
<p>I feel the incompatibility with <code>key: value</code> no longer being converted to positional if need be is simply too big for the gain.</p> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=740652018-09-17T05:53:28Zakr (Akira Tanaka)akr@fsij.org
<ul></ul><p>mame (Yusuke Endoh) wrote:</p>
<blockquote>
<pre><code>def target(*args)
p args
end
def forward(*args, **kw, &blk)
target(*args, **kw, &blk)
end
target(1, 2, 3, {}) #=> [1, 2, 3, {}]
forward(1, 2, 3, {}) #=> [1, 2, 3] on the semantics you proposed ([1, 2, 3, {}] on the current behavior)
</code></pre>
<p>Akr-san said that it would be impossible to create a "perfect" semantics to satisfy all cases. In Ruby 2.X, he said that <code>**empty_hash</code> should be just ignored, and that <code>foo({}, **{})</code> should be always equal to <code>foo({})</code>, even if <code>def foo(opt=42, **kw)</code> receives <code>opt=42</code>. (However, matz showed reluctance to this behavior. So, the solution for this ticket is not decided yet.)</p>
</blockquote>
<p>How about def <code>m(**kw) end</code> binds kw to nil if the last Hash argument is not taken as keyword arguments and<br>
<code>m(**{})</code> add {} and <code>m(**nil)</code> don't add anything to arguments.</p>
<p>{}/nil distinguish there are keyword arguments or not.<br>
This information is what lacks to implement perfect delegation method.</p>
<p>In this behavior, following two should be same behavior (in Ruby 2.X), I think.</p>
<pre><code>def f(*a, *kw) g(*a, *kw) end
def f(*a) g(*a) end
</code></pre> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=740672018-09-17T11:21:37Zakr (Akira Tanaka)akr@fsij.org
<ul></ul><p>akr (Akira Tanaka) wrote:</p>
<blockquote>
<p>In this behavior, following two should be same behavior (in Ruby 2.X), I think.</p>
<pre><code>def f(*a, *kw) g(*a, *kw) end
def f(*a) g(*a) end
</code></pre>
</blockquote>
<p>Oops. <code>def f(*a, *kw) g(*a, *kw) end</code> was wrong.<br>
I wanted to write <code>def f(*a, **kw) g(*a, **kw) end</code></p>
<p>Also, I forgot to describe about symbol/non-symbol key Hash splitting.</p>
<p>Symbol/non-symbol key Hash splitting breaks delegation with<br>
<code>def f(*a, **kw) g(*a, **kw) end</code>.<br>
It can be fixed with removing the Hash splitting, or<br>
move the Hash splitting mechanism to caller from callee.<br>
I think former is more compatible.</p>
<p>I.e. following script should show <code>[{:a=>"b", 1=>2}]</code> twice.<br>
(<code>a</code> in <code>g</code> should be <code>[{:a=>"b", 1=>2}]</code> and<br>
<code>kw</code> should be <code>nil</code>)</p>
<pre><code>% ruby -e '
def f(*a) p a end
def g(*a, **kw) f(*a, **kw) end
f(a: "b", 1 => 2)
g(a: "b", 1 => 2)
'
[{:a=>"b", 1=>2}]
[{1=>2}, {:a=>"b"}]
</code></pre> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=740742018-09-18T00:08:15Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul></ul><p>akr (Akira Tanaka) wrote:</p>
<blockquote>
<p>How about def <code>m(**kw) end</code> binds kw to nil if the last Hash argument is not taken as keyword arguments and<br>
<code>m(**{})</code> add {} and <code>m(**nil)</code> don't add anything to arguments.</p>
</blockquote>
<p>I agree that it is one of the reasonable design choices, but does it break the following code?</p>
<pre><code>def foo(**kw)
kw[:key]
end
foo()
</code></pre>
<p>Applying it in Ruby 2.X would bring a big incompatibility issue, I guess. Rather, it might be worse than separation of keyword argument because the incompatibility is data-dependent: it can not always be fixed syntactically.</p>
<pre><code>def foo(**kw)
@option = kw
end
...
def bar
@option[:key] #=> Null pointer
end
</code></pre> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=740772018-09-18T02:16:37Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<ul></ul><p>akr (Akira Tanaka) wrote:</p>
<blockquote>
<p>How about def <code>m(**kw) end</code> binds kw to nil if the last Hash argument is not taken as keyword arguments and<br>
<code>m(**{})</code> add {} and <code>m(**nil)</code> don't add anything to arguments.</p>
</blockquote>
<p>Very clever :-)</p>
<p>As Mame-san points out, it would be a source of incompatibility. I would really like to know how big though.</p>
<p>I imagine that most cases of <code>**h</code> capture are used to forward them (so no incompatibility).</p>
<p>I looked at <code>DeepCover</code>'s code:<br>
34 cases of <code>def foo(... **option)</code><br>
26 are ok (about 22 simply forward **option)<br>
8 are incompatible (2 of which were simply missing hash splat)<br>
An example: <a href="https://github.com/deep-cover/deep-cover/blob/master/core_gem/lib/deep_cover/tools/content_tag.rb#L6-L9" class="external">https://github.com/deep-cover/deep-cover/blob/master/core_gem/lib/deep_cover/tools/content_tag.rb#L6-L9</a></p> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=740882018-09-18T13:07:44Zakr (Akira Tanaka)akr@fsij.org
<ul></ul><p>mame (Yusuke Endoh) wrote:</p>
<blockquote>
<p>akr (Akira Tanaka) wrote:</p>
<blockquote>
<p>How about def <code>m(**kw) end</code> binds kw to nil if the last Hash argument is not taken as keyword arguments and<br>
<code>m(**{})</code> add {} and <code>m(**nil)</code> don't add anything to arguments.</p>
</blockquote>
<p>I agree that it is one of the reasonable design choices, but does it break the following code?</p>
<pre><code>def foo(**kw)
kw[:key]
end
foo()
</code></pre>
</blockquote>
<p>Yes. I hope that the incompatibility is acceptable.</p>
<p>If it is not acceptable, I have second idea:<br>
We can use a special frozen empty Hash object instead of nil.</p>
<pre><code>NO_KEYWORD_ARGUMENTS = {}.freeze
</code></pre>
<p>In this idea, the condition of "no keyword argument" is represented as<br>
<code>NO_KEYWORD_ARGUMENTS.equal?(x)</code> instead of <code>nil.equal?(x)</code>.</p>
<p>It is still incompatible because it is not modifiable, though.</p>
<pre><code>def foo(**kw) kw[0] = 1 end; foo()
</code></pre>
<p>If kw is bind to NO_KEYWORD_ARGUMENTS, <code>kw[0] = 1</code> raises FrozenError.</p>
<p>Also, more importantly, it adds a new constant and<br>
we (Ruby programmers) need to remember the name.</p> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=740892018-09-18T14:34:29Zakr (Akira Tanaka)akr@fsij.org
<ul></ul><p>akr (Akira Tanaka) wrote:</p>
<blockquote>
<p>If it is not acceptable, I have second idea:</p>
</blockquote>
<p>My third idea is to use an instance variable to distinguish<br>
the two kind of <code>{}</code>.</p>
<p>We can create <code>{}</code> with a special instance variable to distinguish them.</p>
<pre><code>h = {}
h.instance_variable_set(:@no_keyword_arguments, true)
</code></pre>
<p>It can be detected as <code>h.instance_variable_defined?(:@no_keyword_arguments)</code>.</p>
<p>This is almost compatible, I think.</p> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=740912018-09-18T15:46:40Zakr (Akira Tanaka)akr@fsij.org
<ul></ul><p>My fourth idea: FL_USERn flag in `{}'.</p> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=740942018-09-19T01:59:23Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<ul></ul><p>akr (Akira Tanaka) wrote:</p>
<blockquote>
<p>We can create <code>{}</code> with a special instance variable to distinguish them.</p>
</blockquote>
<p>I'm impressed by your creativity :-)</p>
<p>So, would we have the following?</p>
<pre><code>def foo(**opt)
opt.instance_variable_get(:@no_keyword_arguments)
end
foo() # => true
foo({}) # => true ?
foo(**{}) # => false
special = {}; special.instance_variable_set(:@no_keyword_arguments, true)
foo(**special) # => true
foo(special) # => true
</code></pre>
<p>So this would influence how if it gets converted to positional argument, right?</p>
<pre><code>def bar(*args)
args
end
bar(**{}) # => [{}]
bar(**special) # => []
</code></pre> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=788672019-06-25T07:31:43Znobu (Nobuyoshi Nakada)nobu@ruby-lang.org
<ul><li><strong>Has duplicate</strong> <i><a class="issue tracker-1 status-5 priority-4 priority-default closed" href="/issues/15956">Bug #15956</a>: `{*nil}` causes confusing error message</i> added</li></ul> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=788692019-06-25T07:31:58Znobu (Nobuyoshi Nakada)nobu@ruby-lang.org
<ul><li><strong>Has duplicate</strong> <i><a class="issue tracker-1 status-5 priority-4 priority-default closed" href="/issues/15957">Bug #15957</a>: Splatting nil as **kwargs</i> added</li></ul> Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.https://redmine.ruby-lang.org/issues/15078?journal_id=813052019-08-31T02:43:53Zjeremyevans0 (Jeremy Evans)merch-redmine@jeremyevans.net
<ul><li><strong>Status</strong> changed from <i>Open</i> to <i>Closed</i></li></ul><p>Fixed by <a class="issue tracker-2 status-5 priority-4 priority-default closed" title="Feature: "Real" keyword argument (Closed)" href="https://redmine.ruby-lang.org/issues/14183">#14183</a>:</p>
<pre><code>$ ruby -e 'def foo(*args); p args; end; foo(**{}); foo(**Hash.new) '
[]
[]
</code></pre>