Why is ** optional when "splatting" keyword arguments? - ruby

Given this method definition:
def foo(a = nil, b: nil)
p a: a, b: b
end
When I invoke the method with a single hash argument, the hash is always implicitly converted to keyword arguments, regardless of **:
hash = {b: 1}
foo(hash) #=> {:a=>nil, :b=>1}
foo(**hash) #=> {:a=>nil, :b=>1}
I can pass another (empty) hash as a workaround:
foo(hash, {}) #=> {:a=>{:b=>1}, :b=>nil}
But, this looks pretty cumbersome and awkward.
I would have expected Ruby to handle this more like arrays are handled, i.e.:
foo(hash) #=> {:a=>{:b=>1}, :b=>nil}
foo(**hash) #=> {:a=>nil, :b=>1}
And using literals:
foo({b: 1}) #=> {:a=>{:b=>1}, :b=>nil}
foo(b: 1) #=> {:a=>nil, :b=>1}
foo(**{b: 1}) #=> {:a=>nil, :b=>1}
The current implementation looks like a flaw and the way I was expecting it to work seems obvious.
Is this an overlooked edge case? I don't think so. There's probably a good reason that it wasn't implemented this way.
Can someone enlighten me, please?

As for the lack of ** part:
My guess is that, to make method invocation simple, Ruby always once interprets the key: value form without the braces as a hash with omitted braces, whether it is actually going to be interpreted as such hash or as keyword arguments.
Then, in order to interpret that as keyword arguments, ** is implicitly applied to it.
Therefore, if you had passed an explicit hash, it will not make difference to the process above, and there is room for it to be interpreted either as an actual hash or as keyword arguments.
What happens when you do pass ** explicitly like:
method(**{key: value})
is that the hash is decomposed:
method(key: value)
then is interpreted as a hash with omitted braces:
method({key: value})
then is interpreted either as a hash or as a keyword argument.
As for keyword arguments having priority over other arguments, see this post on Ruby core: https://bugs.ruby-lang.org/issues/11967.

Related

optional key word argument needed after splat argument when sub-elements are not arrays

According to this post, it is possible to have an optional key word argument after a splat argument. This works if the splat argument is introducing an array of arrays, but not when it is an array of hashes
For example if the method being called is defined as
def call(*scores, alpha: nil)
puts scores
end
then this works
scores = [[1,2],[3,4]]
call(*scores)
but this does not
scores = [ {a: 1}, {b: 3}]
call(*scores)
giving the following (with ruby 2.4.4)
ArgumentError: unknown keyword: b
but this works
scores = [ {a: 1}, {b: 3}]
call(*scores, alpha: nil)
What is going wrong here?
The splat operator splits the array into arguments.
However if you wrap it in an array it works again but now it's an array inside an array and still treated as a single argument passed to your method.
call([*scores]) #no error
But also to illustrate why you got the error look what happens here:
def call(*scores, alpha: nil)
puts scores.inspect
end
call(*scores[0]) #=> #[[:a, 1]]
UPDATED: Thanks to #Stefan, the reason for the error is actually that your method accepts keyword arguments, which is apparently a known bug. See Keyword arguments unpacking (splat) in Ruby
The reason your last example works is that by passing a 2nd argument to your method, the splat handles the first argument as an array instead of trying to split it up into 2 arguments.
Fore more see Ruby, Source Code of Splat?
Also see https://www.rubyguides.com/2018/07/ruby-operators/#Ruby_Splat_Operator
* converts the array elements to an argument list, so:
call(*[{a: 1}, {b: 3}])
is equivalent to:
call({a: 1}, {b: 3})
Ruby also converts hashes to keyword arguments implicitly (without **), so the above is equivalent to:†
call({a: 1}, b: 3)
Therefore, {a: 1} is treated as a positional argument, and b: 3 (or {b: 3}) as a keyword argument. And because call doesn't take a keyword argument named b you get ArgumentError: unknown keyword: b.
To avoid this, you can pass an extra empty hash‡ as the last argument:
call({a:1}, {b:2}, {})
or:
call(*[{a:1}, {b:2}], {})
or
scores = [{a:1}, {b:2}]
call(*scores, {})
† There's a feature request to add "real" keyword arguments in Ruby 3.
‡ IMO, it would be more correct to use call(*scores, **{}) to indicate "no keyword arguments", but due to a bug this currently doesn't work. You could however use call(*scores, **Hash.new)

Mixing keyword argument and arguments with default values duplicates the hash?

So i discovered this ruby behaviour, which kept me going crazy for over an hour. When I pass a hash to a function which has a default value for hash AND a keyword argument, it seems like the reference doesn't get passed correctly. As soon as I take away the default value OR the keyword argument, the function behaves as expected. Am I missing some obvious ruby rule here?
def change_hash(h={}, rand: om)
h['hey'] = true
end
k = {}
change_hash(k)
k
#=> {}
It works fine as soon as I take out the default or the keyword arg.
def change_hash(h, rand: om)
h['hey'] = true
end
k = {}
change_hash(k)
k
#=> {'hey' => true}
def change_hash(h={})
h['hey'] = true
end
k = {}
change_hash(k)
k
#=> {'hey' => true}
EDIT
Thanks for your answers. Most of you pointed out that ruby parses the hash as a keyword argument in some cases. However, I am talking about the case when a hash has string keys. When I pass the hash, it seems like the value that gets passed is correct. But modifying the hash inside the function doesn't modify the original hash.
def change_hash(hash={}, another_arg: 300)
puts "another_arg: #{another_arg}"
puts "hash: #{hash}"
hash['hey'] = 3
end
my_hash = {"o" => 3}
change_hash(my_hash)
puts my_hash
Prints out
another_arg: 300
hash: {"o"=>3}
{"o"=>3}
TL;DR ruby allows passing hash as a keyword argument as well as “expanded inplace hash.” Since change_hash(rand: :om) must be routed to keyword argument, so should change_hash({rand: :om}) and, hence, change_hash({}).
Since ruby allows default arguments in any position, the parser takes care of default arguments in the first place. That means, that the default arguments are greedy and the most amount of defaults will take a place.
On the other hand, since ruby lacks pattern-matching feature for function clauses, parsing the given argument to decide whether it should be passed as double-splat or not would lead to huge performance penalties. Since the call with an explicit keyword argument (change_hash(rand: :om)) should definitely pass :om to keyword argument, and we are allowed to pass an explicit hash {rand: :om} as a keyword argument, Ruby has nothing to do but to accept any hash as a keyword argument.
Ruby will split the single hash argument between hash and rand:
k = {"a" => 42, rand: 42}
def change_hash(h={}, rand: :om)
h[:foo] = 42
puts h.inspect
end
change_hash(k);
puts k.inspect
#⇒ {"a"=>42, :foo=>42}
#⇒ {"a"=>42, :rand=>42}
That split feature requires the argument being cloned before passing. That is why the original hash is not being modified.
This is particularly tricky case in Ruby indeed.
In your example you have optional argument which is a hash and you have an optional keyword argument at the same time. In this situation if you pass only one hash, Ruby interprets it as a hash which contains keyword arguments. Here is the code to clarify:
change_hash({rand1: 'om'})
# ArgumentError: unknown keyword: rand1
To work around this you can pass two separate hashes into the method with second one (the one for keyword arguments) being empty:
def change_hash(h={}, rand: 'om')
h['hey'] = true
end
k = {}
change_hash(k, {})
k
#=> {'hey' => true}
From the practical point of view it is better to avoid metdhod signature like that in production code, because it is very easy to make an error while using the method.

How does ruby unpack arguments passed into Proc?

a_proc = Proc.new {|a,b,*c| p c; c.collect {|i| i*b }}
puts a_proc[2,2,4,3]
Code above is pretty intuitive according to https://ruby-doc.org/core-2.2.0/Proc.html, a_proc[2,2,4,3] is just a syntax sugar for a_proc.call(2,2,4,3) to hide “call”
But the following (works well) confused me a lot
a=[2,2,4,3]
puts a_proc.call(a)
puts a_proc.call(*a)
It seems very different from a normal function call, cause it doesn't check the number arguments passed in.
However, as expected the method calling semantics will raise an error if using parameters likewise
def foo(a,b,*c)
c.collect{|i| i*b}
end
foo([1,2,3,4]) #`block in <main>': wrong number of arguments (given 1, expected 2+) (ArgumentError)
foo(*[1,2,3,4]) #works as expected
I do not think such an inconsistency as a design glitch, so any insights on this will be appreciated.
Blocks use different semantics than methods for binding arguments to parameters.
Block semantics are more similar to assignment semantics than to method semantics in this regard. In fact, in older versions of Ruby, blocks literally used assignment for parameter binding, you could write something like this:
class Foo; def bar=(val) puts 'setter called!' end end
some_proc = Proc.new {|$foo, #foo, foo.bar|}
some_proc.call(1, 2, 3)
# setter called!
$foo #=> 1
#foo #=> 2
Thankfully, this is no longer the case since Ruby 1.9. However, some semantics have been retained:
If a block has multiple parameters but receives only a single argument, the argument will be sent a to_ary message (if it isn't an Array already) and the parameters will be bound to the elements of the Array
If a block receives more arguments than it has parameters, it ignores the extra arguments
If a block receives fewer arguments than it has parameters, the extra parameters are bound to nil
Note: #1 is what makes Hash#each work so beautifully, otherwise, you'd always have to deconstruct the array that it passes to the block.
In short, block parameters are bound much the same way as with multiple assignment. You can imagine assignment without setters, indexers, globals, instance variables, and class variables, only local variables, and that is pretty much how parameter binding for blocks work: copy&paste the parameter list from the block, copy&paste the argument list from the yield, put an = sign in between and you get the idea.
Now, you aren't actually talking about a block, though, you are talking about a Proc. For that, you need to know something important: there are two kinds of Procs, which unfortunately are implemented using the same class. (IMO, they should have been two different classes.) One kind is called a lambda and the other kind is usually called a proc (confusingly, since both are Procs).
Procs behave like blocks, both when it comes to parameter binding and argument passing (i.e. the afore-described assignment semantics) and also when it comes to the behavior of return (it returns from the closest lexically enclosing method).
Lambdas behave like methods, both when it comes to parameter binding and argument passing (i.e. strict argument checking) and also when it comes to the behavior of return (it returns from the lambda itself).
A simple mnemonic: "block" and "proc" rhyme, "method" and "lambda" are both Greek.
A small remark to your question:
a_proc[2,2,4,3] is just a syntax sugar for a_proc.call(2,2,4,3) to hide “call”
This is not syntactic sugar. Rather, Proc simply defines the [] method to behave identically to call.
What is syntactic sugar is this:
a_proc.(2, 2, 4, 3)
Every occurrence of
foo.(bar, baz)
gets interpreted as
foo.call(bar, baz)
I believe what might be confusing you are some of the properties of Procs. If they are given a single array argument, they will automatically splat it. Also, ruby blocks in general have some interesting ways of handling block arguments. The behavior you're expecting is what you will get with a Lambda. I suggest reading Proc.lambda? documentation and be careful when calling a ruby block with an array.
Now, let's start with the splat operator and then move to how ruby handles block arguments:
def foo(a, b, *c)
c.map { |i| i * b } # Prefer to use map alias over collect
end
foo([1, 2, 3, 4]) # `block in <main>': wrong number of arguments (given 1, expected 2+) (ArgumentError)
foo(*[1, 2, 3, 4]) # works as expected
So in your argument error, it makes sense: def foo() takes at least two arguments: a, b, and however many with *c. The * is the splat operator. It will turn an array into individual arguments, or in the reverse case here, a variable amount of arguments into an array. So when you say foo([1,2,3,4]), you are giving foo one argument, a, and it is [1,2,3,4]. You are not setting b or *c. What would work is foo(1, 1, 1, 2, 3, 4]) for example because you are setting a, b, and c. This would be the same thing: foo(1, 1, *[1,2,3,4]).
Now foo(*[1, 2, 3, 4]) works as expected because the splat operator (*) is turning that into foo(1, 2, 3, 4) or equivalently foo(1, 2, *[3, 4])
Okay, so now that we have the splat operator covered, let's look back at the following code (I made some minor changes):
a_proc = Proc.new { |a, b, *c| c.map { |i| i * b }}
a = [1, 2, 3, 4]
puts a_proc.call(a)
puts a_proc.call(*a)
Remember that if blocks/procs are given a single array argument they will automatically splat it. So if you have an array of arrays arrays = [[1, 1], [2, 2], [3, 3]] and you do arrays.each { |a, b| puts "#{a}:#{b}" } you are going to get 1:1, 2:2, and 3:3 as the output. As each element is passed as the argument to the block, it sees that it is an array and splats it, assigning the elements to as many of the given block variables as it can. Instead of just putting that array in a such as a = [1, 1]; b = nil, you get a = 1; b = 1. It's doing the same thing with the proc.
a_proc.call([1, 2, 3, 4]) is turned into Proc.new { |1, 2, [3, 4]| c.map { |i| i * b }} and will output [6, 8]. It splits up the arguments automatically it's own.

What are all the different uses of the [square brackets] in Ruby?

I'm coming across the square bracket [] syntax quite a bit in Ruby, but it never seems to be doing the same thing. Can anyone list all the different uses for the square brackets [] in Ruby so my mind can get a handle on this seemingly endlessly versatile little symbol? (How is it possible that one symbol can do so much without the Ruby interpreter getting confused?)
Examples:
[] and []= methods
%q[...]
[1,2,3][0]
hash["a"] = 3
ary = []
/[^A-Fa-f0-9]/
"Is a string"[5,3]
The square brackets are in two strict contexts and one optional one:
Defining Arrays
Arrays, i.e. a data structure providing and ordered list of elements can be specified in code by using a syntax like [1,2,3]. This creates an array with the three elements 1, 2, and 3 in exactly that order. you can then iterate over the array using on of the iterator functions like each or map or you can directly access a specific elements by its index id as shown below.
Accessing Elements in Arrays and Hashes
Hashes (also called hashmaps, dictionaries, or associative arrays in other languages) also contain elements similar to arrays. The are different from this in the way that they store their data unordered. Data is not accessed by an integer id as is the case by arrays but with an arbitrary key (commonly a symbol or a string). This is different from e.g. PHP where the same Array type is used for both.
This access to the data is facilitated by methods called [] and []= for both hashes and arrays.
my_array = [:a, :b, :c]
second_element = my_array[1]
# => :b
# notice that the first element in arrays always has the index 0
my_hash = {:a => 1, :b => 2, :c => 3}
element_of_b = my_hash[:b]
# => 2
This is the common use case for the brackets. In Ruby code, you might sometimes see other classes implementing the bracket functions. They do so to allow an access similar to either arrays or hashes and it is then generally expected that these classes behave similar to those but this is in no way enforced. See also Duck Typing.
% Notation
Ruby has a third syntax to create strings (and other objects) apart from the common. Using this syntax, the literal string in code are not enclosed by " or ' but use a special delimiter. It starts with a percent sign, a single character specifying the object to be created and almost any character to chose as a delimiter:
a = %w[foo bar baz]
b = %w{foo bar baz}
c = %wxfoo bar bazx
d = ["foo", "bar", "baz"]
All three example create the same array. Please see the some documentation on how to use this syntax and which other modifier characters are available in Ruby.
While it is common to use brackets here, it is on no way required and can be substituted if required. It is just advisory here as the most common usage of this notation is to create an array of elements from a whitespace-seperated string (as seen above). As such, the usage of brackets makes it further clear that an array is returned as the syntax looks similar to the basic array specification.
Okay, just for my own notes I have gone and had a closer look at this and, building on Holger Just's answer, come up with the following: the use of square brackets in Ruby can be divided into 6 uses, 3 of them a part of Ruby's method definitions and 3 of them semantic constructs.
Method definition
Object creation via class methods Array::[], Hash::[]
Array.[](1,2,3) #=> [1,2,3] #Un-sugared notation
Array["a","b","c"] #=> ["a","b","c"] #Sugared equivalent
Hash["a", 100, "b", 200] #=> {"a"=>100, "b"=>200}
Nothing to do with literal constructors, although it does the same thing.
Element reference via instance methods Array#[], Bignum#[], Continuation#[], Fixnum#[], Hash#[], MatchData#[], Method#[], Proc#[], String#[], Struct#[], Symbol#[], Thread#[], and class methods Dir::[], ENV::[]
ary = [1,2,"abc", [15,16,[26,27]]]
ary.[](2) #=> "abc" #Un-sugared notation
ary[2] #=> "abc" #Sugared equivalent
ary[0,2] #=> [1,2]
ary[3][2][1] #=> 26
[1,2,3][0] #=> 1
"Is a string"[7,3] #=> "rin"
Element assignment via instance methods Array#[]=, Hash#[]=, String#[]=, Struct#[]=, Thread#[]=, and class method ENV::[]=
ary = [1,2,3]
ary.[]=(1,"abc") #=> [1,"abc",3] #un-sugared notation
ary[2] = "def" #=> [1,"abc","def"] #Sugared equivalent
hash = {"a"=>1, "b"=>2}
hash["a"] = 3 #=> {"a"=>3, "b"=>2}
Semantic constructs
Object creation via the array literal constructor
ary = []
There are a bunch of literal constructors in Ruby that create an object of the relevant class via the use of (usually) a simple symbol pair, square brackets being the literal constructor for array objects: Array [], Hash {}, Proc ->(){}, Range .. and ..., Regexp //, String "" and '', Symbol : and :"".
Object creation via the % notation
%q[hello there you] #=> "hello there you" # String % notation
%w[hello there you] #=> ["hello", "there", "you"] # Array % notation
It is not, strictly speaking, square-bracket notation, but rather two-symbol-pair notation of which you can use square brackets if you wish. So %q#hello there you# is equally valid.
Ruby's regular expressions
/[^A-Fa-f0-9]/
Square brackets indicate character classes in Ruby regular expressions.
I did find another use of the [], as a pattern for use in the Dir::glob method, but its supposed to act exactly as it does in regular expressions. Still, it indicates that there are possibly more uses hidden away in Ruby's 1500+ methods.
handy syntax for instantiating structs with square brackets
irb(main):001:0> Point = Struct.new(:x, :y)
=> Point
irb(main):002:0> point = Point[1,2]
=> #<struct Point x=1, y=2>
irb(main):003:0> point.x
=> 1
irb(main):004:0> point.y
=> 2

Ruby: How to loop through an object that may or may not be an array?

I have an each method that is run on some user-submitted data.
Sometimes it will be an array, other times it won't be.
Example submission:
<numbers>
<number>12345</number>
</numbers>
Another example:
<numbers>
<number>12345</number>
<number>09876</number>
</numbers>
I have been trying to do an each do on that, but when there is only one number I get a TypeError (Symbol as array index) error.
I recently asked a question that was tangentally similar. You can easily force any Ruby object into an array using Array.
p Array([1,2,3]) #-> [1,2,3]
p Array(123) #-> [123]
Of course, arrays respond to each. So if you force everying into an array, your problem should be solved.
A simple workaround is to just check if your object responds to :each; and if not, wrap it in an array.
irb(main):002:0> def foo x
irb(main):003:1> if x.respond_to? :each then x else [x] end
irb(main):005:1> end
=> nil
irb(main):007:0> (foo [1,2,3]).each { |x| puts x }
1
2
3
=> [1, 2, 3]
irb(main):008:0> (foo 5).each { |x| puts x }
5
=> [5]
It looks like the problem you want to solve is not the problem you are having.
TypeError (Symbol as array index)
That error tells me that you have an array, but are treating it like a hash and passing in a symbol key when it expects an integer index.
Also, most XML parsers provide child nodes as array, even if there is only one. So this shouldn't be necesary.
In the case of arguments to a method, you can test the object type. This allows you to pass in a single object or an array, and converts to an array only if its not one so you can treat it identically form that point on.
def foo(obj)
obj = [obj] unless obj.is_a?(Array)
do_something_with(obj)
end
Or something a bit cleaner but more cryptic
def foo(obj)
obj = [*obj]
do_something_with(obj)
end
This takes advantage of the splat operator to splat out an array if it is one. So it splats it out (or doesn't change it) and you can then wrap it an array and your good to go.
I was in the same position recently except the object I was working with was either a hash or an array of hashes. If you are using Rails, you can use Array.wrap because Array(hash) converts hashes to an array.
Array({foo: "bar"}) #=> [[:foo, "bar"]]
Array.wrap({foo: "bar"}) #=> [{:foo=>"bar"}]
Array.wrap(123) #=> [123]
Array.wrap([123]) #=> [123]
I sometimes use this cheap little trick:
[might_be_an_array].flatten.each { |x| .... }
Use the splat operator:
[*1] # => [1]
[*[1,2]] # => [1,2]
Like Mark said, you're looking for "respond_to?" Another option would be to use the conditional operator like this:
foo.respond_to? :each ? foo.each{|x| dostuff(x)} : dostuff(foo);
What are you trying to do with each number?
You should try to avoid using respond_to? message as it is not a very object oriented aproach.
Check if is it possible to find in the xml generator code where it is assigning an integer value when there is just one <"number"> tag and modify it to return an array.
Maybe it is a complex task, but I would try to do this in order to get a better OO design.
I don't know much anything about ruby, but I'd assume you could cast (explicitly) the input to an array - especially given that if the input is simply one element longer it's automatically converted to an array.
Have you tried casting it?
If your input is x, use x.to_a to convert your input into an array.
[1,2,3].to_a
=> [1, 2, 3]
1.to_a
=> [1]
"sample string".to_a
=> ["sample string"]
Edit: Newer versions of Ruby seem to not define a default .to_a for some standard objects anymore. You can always use the "explicit cast" syntax Array(x) to achieve the same effect.

Resources