Double splat on `nil` - ruby

My understanding is that a single splat on a non-array object calls to_a and then dissociates the elements apart. And since nil.to_a is defined to be [], the following conversion happens:
[:foo, *nil, :bar]
# => [:foo, *nil.to_a, :bar]
# => [:foo, *[], :bar]
# => [:foo, :bar]
By analogy, I thought that a double splat on a non-hash object calls to_h and then dissociates the key-value pairs apart. And since nil.to_h is defined to be {}, I expected the following conversion to happen:
{"foo" => 1, **nil, "bar" => 2}
# => {"foo" => 1, **nil.to_h, "bar" => 2}
# => {"foo" => 1, **{}, "bar" => 2}
# => {"foo" => 1, "bar" => 2}
But actually, it raises an error: no implicit conversion of nil into Hash. Why does it behave like that?
Edit I am not asking about the reasoning behind the design. I am asking where my thinking is wrong regarding double splat.

Well it's our human being super power to recognize patterns and predict things. However it's not always true. This is one example. Ruby is not consistent in splat and double splat. Your way of thinking is a good way to "remember" but it's not exactly the way Ruby works on splats.
See this bug report for more detail. In this bug report, Ruby's author Matz rather to remove the feature of being able to splat nil than add double splat to nil.

The reason *nil works is because the splat operator works on anything that responds to to_a, and nil.to_a returns []. The reason **nil doesn't work is that nil doesn't respond to to_hash, which is to_a's double-splat counterpart.
If you wanted this behavior, you could monkey-patch NilClass:
class NilClass
def to_hash
{}
end
end
{ "foo" => 1, **nil, "bar" => 2 }
# => { "foo" => 1, "bar" => 2 }

Related

Checking a nested hash key

I have a hash yaml, which is sometimes like:
{"foo" => {"bar" => 10}}
and sometimes like:
{"foo" => nil}
I want to do a certain action depending on whether "bar" is present.
I write this code:
if yaml["foo"] && yaml["foo"].key?["bar"]
...
I'd like to know if there's an idiomatic way to deal with that conditional, especially the first part where I have to check the existence of the parent key.
Hash#dig comes in very handy for cases like yours:
hash = {"foo" => {"bar" => { "baz" => 10}}}
hash.dig('foo', 'bar', 'baz')
#=> 10
Note, that if at any point of digging it returns nil, the method won't blow up but just return nil as result:
hash.dig('foo', 'baz')
#=> nil

Convert string to symbol/keyword

We can convert strings to symbols in the following way:
"string_to_symbol".to_sym
# => :string_to_symbol
How do I convert strings in the new way of defining keywords? Expected outcome:
# => string_to_symbol:
I call keywords dynamically, and I end up using => to assign a value to it. I prefer not to do it that way to keep my code consistent.
No, there isn't.
It's important to note that these two lines do exactly the same:
{ foo: 'bar' } #=> {:foo=>"bar"}
{ :foo => 'bar' } #=> {:foo=>"bar"}
This is because the first form is only Syntactic sugar for creating a Hash using a Symbol as the Key. (and not "the new way of defining keyword in ruby")
If you want to use other types as the key, you still have to use the "hashrocket" (=>):
{ 'key' => 'val' } #=> {"key"=>"val"}
{ 0 => 'val' } #=> {0=>"val"}
Edit:
As #sawa noted in the comments, the question is about passing keyword arguments, not Hashes. Which is technically correct, but boils down to exactly the same (as long as it's Hashes with Symbols as keys:
def foo(bar: 'baz')
puts bar
end
h = {
:bar => 'Ello!'
}
foo(h)
# >> Ello!

Ruby hash vivification weirdness

I'm running ruby 2.2.2:
$ ruby -v
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
Here I am initializing a hash with one key :b that has a value of Hash.new({})
irb(main):001:0> a = { b: Hash.new({}) }
=> {:b=>{}}
Now, I'm going to attempt to auto-vivify another hash at a[:b][:c] with a key 'foo' and a value 'bar'
irb(main):002:0> a[:b][:c]['foo'] = 'bar'
=> "bar"
At this point, I expected that a would contain something like:
{ :b => { :c => { 'foo' => 'bar' } } }
However, that is not what I'm seeing:
irb(main):003:0> a
=> {:b=>{}}
irb(main):004:0> a[:b]
=> {}
irb(main):005:0> a[:b][:c]
=> {"foo"=>"bar"}
This differs from the following:
irb(main):048:0> a = { :b => { :c => { "foo" => "bar" } } }
=> {:b=>{:c=>{"foo"=>"bar"}}}
irb(main):049:0> a
=> {:b=>{:c=>{"foo"=>"bar"}}}
So what is going on here?
I suspect this is something to do with Hash.new({}) returning a default value of {}, but I'm not exactly sure how to explain the end result...
Apologies for answering my own question, but I figured out what is happening.
The answer here is that we are assigning into the default hash being returned by a[:b], NOT a[:b] directly.
As before, we're going to create a hash with a single key of b and a value of Hash.new({})
irb(main):068:0> a = { b: Hash.new({}) }
=> {:b=>{}}
As you might expect, this should make things like a[:b][:unknown_key] return an empty hash {}, like so:
irb(main):070:0> a[:b].default
=> {}
irb(main):071:0> a[:b][:unknown_key]
=> {}
irb(main):072:0> a[:b].object_id
=> 70127981905400
irb(main):073:0> a[:b].default.object_id
=> 70127981905420
Notice that the object_id for a[:b] is ...5400 while the object_id for a[:b].default is ...5420
So what happens when we do the assignment from the original question?
a[:b][:c]["foo"] = "bar"
First, a[:b][:c] is resolved:
irb(main):075:0> a[:b][:c].object_id
=> 70127981905420
That's the same object_id as the .default object, because :c is treated the same as :unknown_key from above!
Then, we assign a new key 'foo' with a value 'bar' into that hash.
Indeed, check it out, we've effectively altered the default instead of a[:b]:
irb(main):081:0> a[:b].default
=> {"foo"=>"bar"}
Oops!
The answer is probably not as esoteric as it might seem at the onset, but this is just the way Ruby is handling that Hash.
If your initial Hash is the:
a = { b: Hash.new({}) }
b[:b][:c]['foo'] = 'bar'
Then seeing that each 'layer' of the Hash is just referencing the next element, such that:
a # {:b=>{}}
a[:b] # {}
a[:b][:c] # {"foo"=>"bar"}
a[:b][:c]["foo"] # "bar"
Your idea of:
{ :b => { :c => { 'foo' => 'bar' } } }
Is somewhat already accurate, so it makes me think that you already understand what's happening, but felt unsure of what was happening due to the way IRB was perhaps displaying it.
If I'm missing some element of your question though, feel free to comment and I'll revise my answer. But I feel like you understand Hashes better than you're giving yourself credit for in this case.

using a string or key-val pair as a method argument

Is there a better way to write this? basically I want to add an argument to a hash. if the argument is a key-val pair, then id like to add it as is. if the argument is a string i'd like to add it as a key with a nil value. the below code works, but is there a more appropriate (simple) way?
2nd question, does calling an each method on an array with two arguments |key, val| automatically convert an array to a hash as it appears to?
#some_hash = {}
def some_method(input)
if input.is_a? Hash
input.each {|key, val| #some_hash[key] = val}
else
input.split(" ").each {|key, val| #some_hash[key] = val}
end
end
some_method("key" => "val")
This gives the result as instructed in the question, but it works differently from the code OP gave (which means that the OP's code does not work as it says):
#some_hash = {}
def some_method(input)
case input
when Hash then #some_hash.merge!(input)
when String then #some_hash[input] = nil
end
end
some_method("foo" => "bar")
some_method("baz")
#some_hash # => {"foo" => "bar", "baz" => nil}
Second question
An array is never automatically converted to a hash. What you are probably mentioning is the fact that the elements of an array within an array [[:foo, :bar]] can be referred to separately in:
[[:foo, :bar]].each{|f, b| puts f; puts b}
# => foo
# => bar
That is due to destructive assignment. When necessary, Ruby takes out the elements of an array as separate things and tries to adjust the number of variables. It is the same as:
f, b = [:foo, :bar]
f # => :foo
b # => :bar
Here, you don't get f # => [:foo, :bar] and b # => nil.

Are there Ruby precedence issues with using Proc.call vs. Proc.[]?

Recently I was having a discussion with a friend about Ruby's Proc. You can call a Proc in one of several ways. One way is to invoke Proc.call:
p = Proc.new { |x| "hello, #{x}" }
p.call "Bob"
=> "hello, Bob"
Another is to use braces, Proc.[]:
p ["Bob"]
=> "hello, Bob"
Are there any potential precedence issues here, or are these two statements completely interchangeable? If not, can you provide an example of a context where different results would be provided?
The #call technique allows the operator precedence to potentially obscure intent:
p = Proc::new do |a1| Proc::new do |a2| "#{a1.inspect}:#{a2.inspect}" end end
p.call([1,2,3]).call [1]
=> => "[1, 2, 3]:[1]"
p.call [1,2,3][1]
=> #<Proc:0x7ffa08dc#(irb):1>
p.call([1,2,3])[1]
=> "[1, 2, 3]:1"
p[[1,2,3]][[1]]
=> "[1, 2, 3]:[1]"
The [] syntax makes the syntactic association of the arguments to the method more robust, but you'd achieve the same effect by putting parentheses around the arguments to Proc#call.

Resources