From: daniel@...42.com Date: 2019-09-04T17:20:59+00:00 Subject: [ruby-core:94779] [Ruby master Feature#14183] "Real" keyword argument Issue #14183 has been updated by Dan0042 (Daniel DeLorme). @jeremyevans0 First, thank you very much for taking your time to engage with me like this. > Here, the intention is to pass the keyword arguments from one method as a positional hash to another method. This is one of the cases that currently breaks in 2.6, that will warn and break in 2.7, and that will be fixed in Ruby 3. With your approach, it will remain broken, since kw in foo will be implicitly converted to keyword arguments to bar. I think in this case the 2.6 behavior is better. Because `:a=>1` is specified without braces, it should ideally remain a keyword all the way down to `bar`. It should not become a Hash in `foo` without explicit conversion. To my eyes, the intention signaled via `bar(kw)` in your example would be to pass-through the keyword arguments. Because kw is not a Hash but KwHash (provisional name). In order to pass it as positional argument you would need to first convert it to Hash via `bar({**kw})` or such. The idea is that a KwHash can only be passed as a kwarg. No automatic conversion. Passing a KwHash `kw` to a method is strictly equivalent to `**kw`. After all the entire point of "Real keyword arguments" is to keep them distinct from the rest right? But syntax is not the only way to do that; this KwHash class would also be a way to achieve the same result. Even though `bar(kw)` may look like a positional argument, it's really a keyword argument, properly and fully differentiated from positional arguments via its class. I realize that's a fairly different interpretation than the current one but I believe it makes sense. Matz may [like](https://bugs.ruby-lang.org/issues/14183#note-45) syntactical separation but I think he would remain open to other possibilities. ```ruby args = [1, 2, hash] foo(*args) #=> args.last is Hash -> positional; warning in 2.7 args = [1, 2, **hash] foo(*args) #=> args.last is KwHash -> keyword ``` I would even go as far as saying that with this KwHash, `bar(kw1, 2, kw3, 4)` must either raise an error or be equivalent to `bar(2, 4, **kw1, **kw3)`, otherwise the separation of keyword and positional arguments doesn't hold. I realize this is not backward compatible, but it's the kind of incompatibility I'm ok with because it fixes incorrect semantics (if it was originally a kwarg it shouldn't suddenly be a Hash). And on the receiver side, even if the kwarg is converted to positional argument because of your compatibility mode, it would still be a KwHash and behave as such unless *explicitly* converted to Hash. The positional/keyword separation is maintained even despite the compatibility mode. I know that code speaks loudest so I would like to write a branch for this idea, but I'm too unfamiliar with the VM code. I wouldn't be able to write something in time to make it for review before the November code freeze. :-( > I understand that keyword argument separation is going to require updating some code. It is not going to be fully backwards compatible for methods that accept keyword arguments. The good news is that if you don't use keyword arguments in your methods, the behavior will remain backwards compatible. Additionally, any cases that will break will be warned about in Ruby 2.7 so you can update the related code. Updating some code in itself is not a problem at all. What makes me uncomfortable is that updating code in order to fix 2.7 warnings can result in code that is no longer compatible with 2.6. This now seems to be the only way to write correct forwarding code? ```ruby if RUBY_VERSION.to_f <= 2.6 def method_missing(*a, &b) @x.send(*a, &b) end else def method_missing(*a, **o, &b) @x.send(*a, **o, &b) end end ``` If it was unavoidable then I'd just say that's the cost of progress. But I'm convinced it's avoidable. Please understand that I'm not clinging to old behavior just as a knee-jerk reaction to change. I've taken your earlier words to heart and spent several hours reading this entire thread carefully as well as related tickets, digesting and pondering the information. So I think I've reached a pretty decent understanding. The current changes are obviously great and fix a lot of problems. It's just that adding keyword separation via class in addition to syntax allows to keep better backward compatibility with **stricter** keyword/positional separation, while still fixing all the issues related to the previous implementation. I think that's worth serious consideration. And as a bonus, it even becomes easier to optimize the KwHash implementation specifically for keyword arguments. Thank you for your patience and forgive the verbosity; I find it hard to convey the nuance of my argument. This is my last post about the KwHash idea (unless you have questions :-). If I still can't gain the interest of the ruby maintainers with this... I guess we'll just have to go down the backward-incompatible route. ---------------------------------------- Feature #14183: "Real" keyword argument https://bugs.ruby-lang.org/issues/14183#change-81397 * Author: mame (Yusuke Endoh) * Status: Closed * 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: