Understanding ruby splat in ranges and arrays
I believe the problem is that splat can only be used as an lvalue, that is it has to be received by something.
So your example of *(1..9).map
fails because there is no recipient to the splat, but the [*1..9].map
works because the array that you are creating is the recipient of the splat.
UPDATE:
Some more information on this thread (especially the last comment): Where is it legal to use ruby splat operator?
What does * mean at the beginning of a range?
As it is stated in the documentation:
You can turn an
Array
into an argument list with*
(or splat) operator:arguments = [1, 2, 3]
my_method(*arguments)
The same might be done for Range
:
arguments = 1..3
my_method(*arguments) # essentially the same as my_method(1, 2, 3)
Also splat operator is allowed before ranges inside the array declaration to implicitly convert Range
to Array
:
[*1..3]
#⇒ [1, 2, 3]
How is the splat operator understood when applied to a range expression?
The splat *
converts the object to an list of values (usually an argument list) by calling its to_a
method, so *1..4
is equivalent to:
1, 2, 3, 4
On its own, the above isn't valid. But wrapped within square brackets, [*1..4]
becomes:
[1, 2, 3, 4]
Which is valid.
You could also write a = *1..4
which is equivalent to:
a = 1, 2, 3, 4
#=> [1, 2, 3, 4]
Here, the list of values becomes an array due to Ruby's implicit array assignment.
Where is it legal to use ruby splat operator?
First, precedence isn't an issue here, because foo = bar || (*zap)
works no better. The general rule of thumb is that you cannot perform additional operations on a splat. Even something as simple as foo = (*zap)
is invalid. This applies to 1.9 as well.
Having said that, what do you expect foo = bar || *zap
to do, if it worked, that is different than foo = bar || zap
? Even in a case like a, b = bar || *zap
(which also doesn't work), a, b = bar || zap
accomplishes what I'd assume would be the same thing.
The only situation where this might make any sense is something like a, b = foo, bar || *zap
. You should find that most cases where you would want to use this are covered by a, b = foo, *(bar || zap)
. If that doesn't cover your case, you should probably ask yourself what you really hope to accomplish by writing such an ugly construct.
EDIT:
In response to your comments, *zap || bar
is equivalent to *(zap || bar)
. This demonstrates how low the splat's precedence is. Exactly how low is it? The best answer I can give you is "pretty low".
For an interesting example, though, consider a method foo
which takes three arguments:
def foo(a, b, c)
#important stuff happens here!
end
foo(*bar = [1, 2, 3])
will splat after the assignment and set the arguments to 1, 2, and 3 respectively. Compare that with foo((*bar = [1, 2, 3]))
which will complain about having the wrong number of arguments (1 for 3).
Confusion with splat operator and Range in Ruby
I think of parallel assignment as setting an array of variables equal to another array with pattern matching.
One point is that a range is a single value until you convert it to an array or splat it. For instance [1..5]
which is a one element array of the range 1..5
and not [1,2,3,4,5]
. To get the array of ints you need to do (1..5).to_a
or [*(1..5)]
The first one i think is the trickiest. If the splatted var is assigned to one element, the var itself must be a one-element array:
*a = 5
a
# => [ 5 ]
For the next two, splat takes 0 or more not already assigned values into an array. So the following makes sense:
*a, b = (1..8)
is like
*a, b = "hey"
which is like
*a, b = [ "hey" ]
so *a
is []
and b
is "hey"
and by the same logic that if *a
is nothing, a
must be an empty array. Same idea for
a, *b = (1..5)
For the next one, the range is splatted, so the assignment makes a lot of sense again:
[*(2..4), 9, 5]
# => [2, 3, 4, 9, 5]
And parallel assignment with a splat again. Next one is similar:
[*3, *4, *5]
# => [3, 4, 5]
So that's like
a = 3, 4, 5
which is like
a = [3, 4, 5]
Ruby * operator before array
*
is the splat operator. It is used to split an array into a list of arguments.
line.split(/=|;/)
returns an array. To create a Hash, each element of the array must be passed as an individual parameter.
Correct way to populate an Array with a Range in Ruby
You can create an array with a range using splat,
>> a=*(1..10)
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
using Kernel
Array
method,
Array (1..10)
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
or using to_a
(1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Why was I able to get away with not using a splat operator sometimes
Well, let's check it out. There is no any magic behind attributes.extract!
in the end. Here is an actual implementation of this method from Rails source code:
def extract!(*keys)
keys.each_with_object(self.class.new) { |key, result|
result[key] = delete(key) if has_key?(key)
}
end
Link: click. As you can see, it creates new hash, goes over the keys
one by one and moves value from self
to this new array. So, if you give an Array argument to this method then key
in the block will be an Array as well. So, it won't be found. So, no way it may work for array argument. The only one possibility is that something else is passed instead of User::HOME_ADDRESS_FIELDS
.
Create new arrays from nested arrays based on corresponding indexes
You are simply transposing the inner arrays:
arrays.map(&:transpose)
#=> [
# [
# ["ab", "ik", "rs"],
# ["cd", "lm", "tu"],
# ["ef", "no", "vw"],
# ["gh", "pq", "xy"]
# ],
# [
# ["z1", "89", "GH"],
# ["23", "AB", "IJ"],
# ["45", "CD", "KL"],
# ["67", "EF", "MN"]
# ]
# ]
Related Topics
Specifying Content Type in Rspec
Case Expression Different in Ruby 1.9
Ruby 1.9.2 - Read and Parse a Remote CSV
Sidekiq Worker Not Getting Triggered
Catching Timeout Errors with Ruby Mechanize
Spacing Around Parentheses in Ruby
Parsing Large Xml Files W/ Ruby & Nokogiri
Ftps (Tls/Ssl) from Ruby on Rails App
Ruby Scope of Data After _End_
How to Convert Activerecord Table Name to Model Class Name
Rails: Wkhtmltopdf Runtimeerror (Location of Wkhtmltopdf Unknown)
Stack Level Too Deep When Using Carrierwave Versions
How to Disable All Form_For Input Fields in Ruby on Rails App
Ruby Selenium Webdriver Unable to Find Mozilla Geckodriver
How to Add "Somewhere" a 'Before(:Each)' Hook So That All Spec File Can Run It
How to Source Environment Variables for a Command Shell in a Ruby Script