Expanding empty hash in variable with double splat in Ruby - ruby

I got this strange behavior trying to expand the hash variable using double splat. Do not know why this is happening.
My ruby version
ruby 2.2.6p396 (2016-11-15 revision 56800)
Scenario
class MyClass
def my_method; end
end
MyClass.new.my_method(*[]) # returns nil
MyClass.new.my_method(**{}) # returns nil
MyClass.new.my_method(*[], **{}) # returns nil
# Using variables
values = []
k_values = {}
MyClass.new.my_method(*values) # returns nil
MyClass.new.my_method(**k_values) # *** ArgumentError Exception: Wrong number of arguments. Expected 0, got 1.
MyClass.new.my_method(*values, **k_values) # *** ArgumentError Exception: Wrong number of arguments. Expected 0, got 1.
# In summary
MyClass.new.my_method(**{}) # returns nil
MyClass.new.my_method(**k_values) # *** ArgumentError Exception: Wrong number of arguments. Expected 0, got 1.
Does any one knows why this is happening? Is this a bug?

Yes, it very looks like a bug
def foo(*args)
args
end
foo(**{})
# => []
h = {}
foo(**h)
# => [{}]
It passes empty hash as first argument in case of double splat of variable.
My version is ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin16]

Related

ruby code with the NoMethodError

When running the following Ruby code:
#!/usr/bin/env ruby
ar=[]
class String
def to_int
self == self.to_i
end
end
ARGV.each do |a|
ar.push("#{a}")
end
ar.map(&:to_int).sort
ar.each do |x|
print x + " "
end
puts ""
I am getting the following error:
example.rb:14:in `sort': undefined method `<=>' for false:FalseClass (NoMethodError)
This program needs to be running with the command line argument with a list of numbers. Any help will be appreciated.
ARGV.sort.each { |x| print x + " " }
puts
class String
def to_int
self == self.to_i
end
end
This to_int method will return either a true or false. So when you run this line: ar.map(&:to_int).sort, the map method will map the entire array into true or false.
You array will look like [false,false,true,false], which will fail when you run the sort function.
I'm not sure what the purpose of the to_int function is, you just need to map with the simple to_i function, then sort it.
ar.map!(&:to_i).sort
Make sure you use the map! so your original array is modified.
If you do map the array into integers, you will have to modify the print line to
ar.each do |x|
print x.to_s + " "
end
Otherwise you will get an error:
String can't be coerced into Fixnum
When I run this using Ruby 2.3.0, I don't get that error.
I tried Ruby 2.0.0p648 (which comes with OS X), 2.1.5, and 2.2.4, and they don't raise that error either.
It's a bit unclear to me what you're trying to accomplish here.
You're doing things that don't make any sense, but I'll assume you're trying to learn Ruby and you're just trying different things.
#!/usr/bin/env ruby
ar=[]
# This is "monkey patching" String, and is a bad practice.
class String
# A method named "to_int" implies a conversion to integer. But the "to_i" method already does
# that, and this method doesn't convert to an integer, it converts to a boolean.
def to_int
# Comparing the string to itself as an integer. Why would it ever be true?
self == self.to_i
end
end
# I'm assuming that this is intended to convert the argument list to a list of strings.
# But ARGV should already a list of strings.
# And this would be better done as `ar = ARGV.map(&:to_s)`
ARGV.each do |a|
ar.push("#{a}");
end
# This creates an array of values returned by `to_int`, then sorts the array.
# Since `String#to_int` (defined above) returns booleans, it's an array of "false" values, so
# sorting does nothing. But then the value of the `sort` is ignored, since it's not assigned to
# a variable. If you want to modify the order of an existing array, use `sort!`.
# But if you're trying to sort the array `ar` by the numeric values, `ar.sort_by(&:to_i)` would
# do that.
ar.map(&:to_int).sort
ar.each do |x| print x + " "; end
puts ""
It seems like you're trying to print the arguments in their numeric order.
This can be accomplished with
puts ARGV.sort_by(&:to_i).join(" ")

Ruby automatically expands Hash into keyword arguments without double splat

The below Ruby code results in: unknown keyword: a (ArgumentError):
def test(x={}, y: true); end
test({a:1})
Why? I would expect this to happen with test(**{a:1}), but I don't understand why my hash is being automagically expanded without the double splat.
Since x is optional, hash moves over to kwarg argument.
Unspecified keywords raise error in that case:
def foo(name:)
p name
end
foo # raises "ArgumentError: missing keyword: name" as expected
foo({name: 'Joe', age: 10}) # raises "ArgumentError: unknown keyword: age"
Check out this article
I'd also find it a bug as it behaves quite inconsistently, only for hashes with keys of Symbol type:
test({a:1}) # raises ArgumentError
test({'a' => 1}) # nil, properly assigned to optional argument x
method(:test).parameters
=> [[:opt, :x], [:key, :y]]
You may pass both arguments and it starts to assign them properly, but this is not a solution.
test({a:1}, {y:false}) # nil
Any reason why this is not a bug but an expected behavior ?

Ruby strange behavior in keyword arguments mixed with positional

Following code:
class Test
attr_reader :args
def initialize(arg1={}, arg2: 'value2')
#args = [arg1, arg2]
end
end
t = Test.new({key1: 'value1'})
puts t.args
I've expected to get printed out array with content [{key1: 'value1'}, 'value2']
but I'm getting:
test.rb:11:in `new': unknown keywords: val1, val2 (ArgumentError)
from test.rb:11:in `<main>'
To be more strange, with the same testing class invoked with {val1: 'value', val2: 'value2'}, arg2: 1) as arguments i'm getting output: {:val1=>"value", :val2=>"value2"}
Where is the source of this behaviour, i'm missing something or it's a bug?
Ruby versions 2.1.1 and 2.1.2 was tested.
I'm currently using Ruby 2.1.0p0.
The "problem" can be simplified a little with the following example:
def foo(arg1 = {}, arg2: 'value1')
[arg1, arg2]
end
Here, the method foo has one OPTIONAL argument arg1 (with default {}) and one OPTIONAL keyword argument, arg2.
If you call:
foo({key1: 'value1'})
You get the error:
ArgumentError: unknown keyword: key1
from (irb):17
from /home/mark/.rvm/rubies/ruby-2.1.0/bin/irb:11:in `<main>'
The reason is that Ruby is attempting to match the only argument you gave (with keyword key1) to the only OPTIONAL keyword argument which is keyword arg2. They don't match, thus the error.
In the next example:
foo({val1: 'value', val2: 'value2'}, arg2: 1)
We get the result:
=> [{:val1=>"value", :val2=>"value2"}, 1]
This makes sense because I provided two arguments. Ruby can match arg2: 1 to the second keyword argument and accepts {val1: 'value', val2: 'value2'} as a substitute for the first optional argument.
I do not consider the behaviors above a bug.
Obviously, parameters resolution works the other way around from what you expected. In addition to konsolebox' answer, you can fix it by calling the constructor with an additional empty hash:
Test.new({key1: 'value1'}, {})
Do it this way instead:
class Test
attr_reader :args
def initialize(arg1={}, arg2 = 'value2') ## Changed : to =.
#args = [arg1, arg2]
end
end
t = Test.new({key1: 'value1'})
puts t.args.inspect
Output:
[{:key1=>"value1"}, "value2"]

Can't change Strings into int in `[]`

In ruby, I try to convert a String to Int in operator '[]' But failed.
Here is the code (my input is 14 45):
STDIN.gets.split(/\s+/).each do |str|
book = tags[str.to_i] # book is just a new variable. tags is an array
end
the ruby will stop with an error:
in '[]': no implicit conversion of String into Integer (TypeError)
So I change my code to follows(this one works well.):
STDIN.gets.split(/\s+/).each do |str|
number = str.to_i # for converting
book = tags[number]
end
This one works well. But I must add one more line for converting. Is there a good way for avoiding that line?
my version of ruby is: $: ruby --version ==> ruby 2.0.0p0 (2013-02-24 revision39474) [i686-linux]
Hi Guys, Please let me KNOW why you still want to close this topic. THANKS.
The error message you are getting will definitely only ever occur when you pass a String as an index to Array#[]. So you are probably not showing us the source that you are actually running. Consider:
a = [1,2,3]
str = 'string'
str.to_i
#=> 0
a[str.to_i]
#=> 1
number = str.to_i
#=> 0
a[number]
#=> 1
a['string']
# TypeError: no implicit conversion of String into Integer
By the way, the error message in your question is specific to Ruby 2.0.0:
RUBY_VERSION
#=> "2.0.0"
a = [1,2,3]
a['string']
# TypeError: no implicit conversion of String into Integer
Whereas in Ruby 1.9.3-p392 you will get this error message:
RUBY_VERSION
#=> "1.9.3"
a = [1,2,3]
a['string']
# TypeError: can't convert String into Integer

Why do I get different results in IRB and a script?

Is there a difference in how IRB and Ruby execute some expressions?
These expressions give different results in IRB and when run from the command line. The question is, which one is correct?
IRB:
>> s = 'hello'
=> "hello"
>> s.size
>> s[s.length] = '!'
IndexError: index 5 out of string
from (irb):31:in `[]='
from (irb):31
>>
And in the normal script:
s = 'hello'
s[s.length] = '!'
puts s
laptop user$ ./prgruby.rb
hello!
Here is the doc of String#[] for 1.8.7 :
str[fixnum] = fixnum
The forms that take a Fixnum will raise an IndexError if the value is
out of range
Here is the same doc for 1.9.3 : the same definition is present
After test, what happen in Ruby 1.9.3 is s.length is not out of range for assignation. This make sense at it is the end of the string : you do not have to arbitrary fill the missing indexes but I guess it may be or should be documented somewhere ?

Resources