Issue #14183 has been updated by jeremyevans0 (Jeremy Evans).


mame (Yusuke Endoh) wrote:
> > # My Proposed Alternative
> 
> Just confirm.  I think your following snippet lacks `unless kw.empty?`, right?

Correct.  Sorry about that.

> 
> ```
> # Add keyword arguments
> def foo(*args, output: $stdout, **kw)
>   args << kw unless kw.empty? # This "unless" modifiler is needed, I think.
>   output.puts args.inspect
> end
> foo(key: 42)
> # => [{:key=>42}]
> ```
> 
> And, `foo({})` will assign `args = [{}]`, right?

Correct, in Ruby 3 assuming the behavior changes for keyword arguments are in effect.  With your branch+my patch:

```ruby
foo({})
# warning: The last argument for `foo' (defined at (irb):1) is used as the keyword parameter
# output: []
```

With your branch+my patch, you can work around the warning by passing an empty keyword splat:

```ruby
foo({}, **{})
# output: [{}]
```

> If so, your proposal looks good enough to me.

Great!

> > # Issues with keyword-argument-separation branch
> 
> Thank you for checking my prototype deeply!
> 
> > The rb_no_keyword_hash approach breaks modification of the hash, which I believe is unexpected:
> 
> Yes.  Akr and I knew that this would bring some incompatibility.  We expected that the incompatibility should be small, but I noticed that it doesn't, unfortunately.  It should be fixed by something like special instance variable, as you said.

One issue with the special instance variable approach is that if you add an entry to the keyword hash, you probably do want to pass the keyword arguments even if the instance variable is present.  So you would want to also check that the hash is still empty.  Example:

```ruby
def foo(*a, **kw)
  kw[:b] = 1 if a.length == 1
  bar(*a, **kw)
end

def bar(*a)
  a
end

foo
# => []

foo(1)
# => [1, {:b=>1}]
```

In my patch, we skip passing all empty keyword argument splats as hashes, so it should already handle this case (once the keyword splat hash is no longer frozen).

> > The warning seems inconsistent. For positional splats, you get warned if the braceless hash is the first argument, but not if it is a subsequent argument:
> 
> Good catch, I didn't intend it.  I fixed my branch.  And this brings more warnings ;-) so I re-examined our internal Rails app (explained later).  And the modification of my branch made your patch inapplicable, so I'm attaching a modified version of your patch.

Thank you, I will try to do some more testing with your revised branch and the modified patch next week.

> > Behavior is different for methods defined in C, as C methods are always passed a hash, so the brace, braceless, and splat forms all work:
> 
> We will keep the compatibility of C API because it would be more difficult to fix.  Regardless of whether the brace is used, C method consisitently receives a hash.

I figured that would be difficult to change.  I think my patch would make C-methods perform the same as Ruby methods without keywords, which is probably best for compatibility other Ruby implementations that do not implement the C-API and use alternatives written in Ruby.

> By the way, ko1 is now working on replacement of built-in methods from C to Ruby.  (He will talk about this plan in RubyKaigi: [Write a Ruby interpreter in Ruby for Ruby 3](https://rubykaigi.org/2019/presentations/ko1.html#apr18).)
> His main motivation is performance, but this will also reduce the problem of C methods that receives keyword arguments.

That is very interesting.  I will make sure to attend ko1's presentation.

> And, thank you for your alternative patch.  I tried it with our internal Rails app again.  It emitted about 8k warnings (much less than 120k!).  Unfortunately I have no enough time to analyze the result, but it looks that no modification is required in our code base.  Great.

It is great to hear that it required no changes in your app's code, only requiring changes in gems that are passing hashes to methods that expect keywords.

----------------------------------------
Feature #14183: "Real" keyword argument
https://bugs.ruby-lang.org/issues/14183#change-77371

* Author: mame (Yusuke Endoh)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: Next Major
----------------------------------------
In RubyWorld Conference 2017 and RubyConf 2017, Matz officially said that Ruby 3.0 will have "real" keyword arguments.  AFAIK there is no ticket about it, so I'm creating this (based on my understanding).

In Ruby 2, the keyword argument is a normal argument that is a Hash object (whose keys are all symbols) and is passed as the last argument.  This design is chosen because of compatibility, but it is fairly complex, and has been a source of many corner cases where the behavior is not intuitive.  (Some related tickets: #8040, #8316, #9898, #10856, #11236, #11967, #12104, #12717, #12821, #13336, #13647, #14130)

In Ruby 3, a keyword argument will be completely separated from normal arguments.  (Like a block parameter that is also completely separated from normal arguments.)
This change will break compatibility; if you want to pass or accept keyword argument, you always need to use bare `sym: val` or double-splat `**` syntax:

```
# The following calls pass keyword arguments
foo(..., key: val)
foo(..., **hsh)
foo(..., key: val, **hsh)

# The following calls pass **normal** arguments
foo(..., {key: val})
foo(..., hsh)
foo(..., {key: val, **hsh})

# The following method definitions accept keyword argument
def foo(..., key: val)
end
def foo(..., **hsh)
end

# The following method definitions accept **normal** argument
def foo(..., hsh)
end
```

In other words, the following programs WILL NOT work:

```
# This will cause an ArgumentError because the method foo does not accept keyword argument
def foo(a, b, c, hsh)
  p hsh[:key]
end
foo(1, 2, 3, key: 42)

# The following will work; you need to use keyword rest operator explicitly
def foo(a, b, c, **hsh)
  p hsh[:key]
end
foo(1, 2, 3, key: 42)

# This will cause an ArgumentError because the method call does not pass keyword argument
def foo(a, b, c, key: 1)
end
h = {key: 42}
foo(1, 2, 3, h)

# The following will work; you need to use keyword rest operator explicitly
def foo(a, b, c, key: 1)
end
h = {key: 42}
foo(1, 2, 3, **h)
```

I think here is a transition path:

* Ruby 2.6 (or 2.7?) will output a warning when a normal argument is interpreted as keyword argument, or vice versa.
* Ruby 3.0 will use the new semantics.

---Files--------------------------------
vm_args.diff (4.19 KB)
vm_args_v2.diff (4.18 KB)


-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>