Bug #8507
closedKeyword splat does not convert arg to Hash
Description
A single splat is useful to convert an object to an array as it passes from one method to the next, so I expected the double-splat to do the same and convert the object to a hash.
def splat *args
p args
end
def double_splat **kwargs
p args
end
splat(*nil) # []
splat(**nil) # TypeError: no implicit conversion of nil into Hash
For the sake of consistency, wouldn't it make sense to wrap the double-splatted arg with Hash()
the same way a single-splatted arg is wrapped with Array()
?
Files
Updated by matz (Yukihiro Matsumoto) over 11 years ago
- Status changed from Open to Feedback
I hate consistency as a reason to change. Do you really want to do splat(**nil)
?
Matz.
Updated by stephencelis (Stephen Celis) over 11 years ago
Is there logic behind the current state that I'm unaware of? Consistency is predictability, which is important when learning a new interface. If splatted nils are swallowed, why not double-splatted nils? One raising where the other doesn't surprised me.
With args, you can be sure that the value passed is an array when splatted out. Splatting itself is seen as a method of conversion for me and other Rubyists I've spoken with. The fact that double-splatting makes no attempt to convert the value seems to make it useless as a convention. What is the difference between the following method invocations?
def kwmethod **kwargs
p kwargs
end
hash = { hello: 'world' }
kwmethod(hash)
kwmethod(**hash)
With *args
, there is a clear reason to splat vs. not. Why not create a similar analog with keyword arguments?
Updated by stephencelis (Stephen Celis) over 11 years ago
To return to the original question:
Do you really want to do
splat(**nil)
?
I find that Ruby's flexibility has been nice when providing public interfaces in gems and libraries. It's nice to be able to pass in *nil
to a public method and have it discard the argument as unnecessary. Likewise, it's nice to be able to accept **nil
as the keyword argument to a public interface and have it discard the argument and look to its default keyword arguments instead.
Updated by matz (Yukihiro Matsumoto) over 11 years ago
You didn't explain why "it is nice". Could elaborate? Do we really need it?
FYI, in the early stage of Ruby, types are more flexible; integers can be treated as strings, nil can be treated as empty array. nil.to_a
=> []
is a left-over of the old days.
For consistency's sake, I'd rather remove nil.to_a
if we don't see compatibility problems.
Matz.
Updated by nobu (Nobuyoshi Nakada) over 11 years ago
- Description updated (diff)
Updated by stephencelis (Stephen Celis) over 11 years ago
Hm... nil.to_h => {}
is brand-new and creates an analog to nil.to_a
, so why would we remove nil.to_a
?
(**nil)
would be another tool that a programmer could use when handing arguments off between methods. One can currently use a single splat to convert nil
or an empty array into an empty arg list. The ability to use double-splat to convert nil
, an empty array, or an empty hash into a keyword arg list is equally powerful. It means flexibility in input and in handing off and converting arguments among different methods.
For instance, there was a recent moment where I thought I could elegantly double-splat an argument passed to a method in order to convert it to a hash without using kwargs || {}
, similar to what I've been able to do with a single splat and an args array (or, in some cases, a nil
arg). The fact that I was unable to convert the object using a double-splat (while I can convert an object using a single-splat) seemed unpredictable. Rubyists have grown to expect that a single splat converts arguments passed in a certain way. I was hoping that handling the double-splat in a similar way would be an elegant way to resolve the current dissonance.
Here's a contrived example:
def outer_method **options
inner_method(**options[:inner_options])
end
The ability to pass a value that may be a hash or may be nil
as keyword arguments simplifies code (you don't have to check for nil
) and makes it apparent as to what kind of value the inner method is taking (the same way a single splat denotes an args list).
I realize that keyword arguments are relatively new. I've already come up against some roadblocks using them (and finding their behavior fundamentally change between patchlevel 0 and 195). I think it's a great time to refine how we expect them to behave.
Again, the unexpected difference in behavior:
Array(nil) # => []
nil.to_a # => []
[*nil] # => []
Hash(nil) # => {}
nil.to_h # => {}
{**nil} # TypeError: no implicit conversion of nil into Hash
Updated by kainosnoema (Evan Owen) over 11 years ago
I'm glad this is getting addressed. Handling nil values is one of the most tiring and error-prone aspects of coding in Ruby. Much has been said about the mistake of introducing NULL references in languages (http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare, among others), and there are considerable efforts being made in many places to remove much of the pain that they cause.
I've also personally run into the situation shown in stephencelis's example above, and was quite surprised and disappointed by it. I'm 100% in support of any effort to gracefully handle nil values when coercing. The consistency added by stephencelis's patch is a big win, IMO.
Updated by stephencelis (Stephen Celis) over 11 years ago
Tried my best to thoughtfully address questions before: http://bugs.ruby-lang.org/issues/8507#note-6
Does anyone have any additional thoughts on the matter?
Updated by mcmire (Elliot Winkler) over 11 years ago
So let's think about why we have splat(*nil)
. Ordinarily you wouldn't write this code like that of course, but you might have a variable foo and unbeknownst to you (or perhaps not), foo happens to be nil, so you end up with splat(*nil)
. So I think we're getting a little distracted here by the nil
.
Let's think about why we use splat at all. We use it because we have an array, and we want to call a method and spread the array across to create a list of arguments. So if foo
is [1, 2, 3]
, splat(*foo)
is splat(1, 2, 3)
.
Why don't we have an equivalent splat syntax for hashes? Well because we don't need it. You can just say splat(foo)
, assuming foo
is a hash, and it just works. What would splat(**foo)
even do?
For instance say #splat
is defined as:
def splat(foo: 'bar', **kwargs)
end
Now assuming foo is {foo: 'bliz', a: 'b', c: 'd'}
, what would splat(**foo)
do? Wouldn't this just end up calling splat(foo: 'bliz', a: 'b', c: 'd')
, and therefore be the same as calling splat(foo)
?
So while I agree for consistency's sake that foo(**whatever)
should do something just as foo(*whatever)
does something, I don't really know if this syntax would be used.
Updated by stephencelis (Stephen Celis) over 11 years ago
Thanks for weighing in. Given that nil is so prevalent in the language, I don't think we can ignore it.
Also, **foo
does already exist in the language, and it does do something (validates that the hash acts like kwargs and raises a TypeError
if any of the keys aren't symbols).
Meanwhile, what if splat were defined as:
def splat(foo = {}, **kwargs)
p foo, kwargs
end
What should happen with something like the following?
splat(foo: 'bar', a: 'b', **{c: 'd'})
Should it return:
[{}, {:foo=>'bar', :a=>'b', :c=>'d'}]
Or should it return:
[{:foo=>'bar', :a=>'b'}, {:c=>'d'}]
Is it something one can easily guess?
And what should this do?
splat(foo: 'bar', 'baz' => 'fizz', buzz: 'bucket')
Heck, what if {**hash_with_string_keys}
actually did something like Active Support's symbolize_keys
?
Right now, the behavior seems ambiguous in general.
Updated by hsbt (Hiroshi SHIBATA) almost 11 years ago
- Target version changed from 2.1.0 to 2.2.0
Updated by stephencelis (Stephen Celis) over 10 years ago
Another issue with keyword args and consistency with args:
- *args are immutable
- **kwargs are mutable
def splat *args
args << 'foobar'
end
arr = []
splat(*arr)
arr # => []
def double_splat **kwargs
kwargs[:foo] = 'bar'
end
hash = {}
double_splat(**hash)
hash # => {:foo=>"bar"}
Updated by marcandre (Marc-Andre Lafortune) over 10 years ago
Stephen Celis wrote:
- **kwargs are mutable
def double_splat **kwargs kwargs[:foo] = 'bar' end hash = {} double_splat(**hash) hash # => {:foo=>"bar"}
I believe this is a bug. Matz, could you confirm?
Updated by naruse (Yui NARUSE) almost 7 years ago
- Target version deleted (
2.2.0)
Updated by mame (Yusuke Endoh) almost 5 years ago
- Has duplicate Bug #9291: array splatting a nil works, but hash splatting a nil does not added
Updated by mame (Yusuke Endoh) almost 5 years ago
- Status changed from Feedback to Rejected
- Backport deleted (
1.9.3: UNKNOWN, 2.0.0: UNKNOWN)
As far as I understand, no one showed any actual use case that justifies adding nil.to_h #=> {}
.
I agree with matz, it would be better to remove nil.to_a #=> []
instead of adding nil.to_h #=> {}
for this issue. I don't think it is possible for the compatibility reason, though.