Bug #10837
closedspatting a single element array produces an array instead of a single value for return and next
Description
irb(main):013:0> ->{return *[1]}.()
=> [1]
irb(main):014:0> ->{next *[1]}.()
=> [1]
*[x] should mean x as it already does for arguments
Updated by nobu (Nobuyoshi Nakada) over 9 years ago
- Status changed from Open to Rejected
It's similar to return *[1, 2]
.
Updated by bughit (bug hit) over 9 years ago
Nobuyoshi Nakada wrote:
It's similar to
return *[1, 2]
.
No it's not similar,
return *[1, 2]
means return 1, 2
similar would be:
return *[1]
means return 1
in general, splatting in a context that takes a coma separated list of items (method args, rescue, return, next, multiple assignment) is supposed to destructure the array. The array should be gone, only the elemnts remain, so returning the [1]
is wrong.
Updated by bughit (bug hit) over 9 years ago
bug hit wrote:
Nobuyoshi Nakada wrote:
It's similar to
return *[1, 2]
.No it's not similar,
return *[1, 2]
meansreturn 1, 2
similar would be:
return *[1]
meansreturn 1
in general, splatting in a context that takes a coma separated list of items (method args, rescue, return, next, multiple assignment) is supposed to destructure the array. The array should be gone, only the elemnts remain, so returning the
[1]
is wrong.
some examples
method_call *[a]
is method_call a
as expected
rescue *[a]
is rescue a
as expected
[*[a]]
is [a]
as expected
b = *[a]
is b = [a]
why?
(next|return) *[a]
is (next|return) [a]
why?
the intuitive, expected and logical behavior would be for *array
to consistently destructure, when used in a coma separated list context
Updated by bughit (bug hit) over 9 years ago
bug hit wrote:
bug hit wrote:
Nobuyoshi Nakada wrote:
It's similar to
return *[1, 2]
.No it's not similar,
return *[1, 2]
meansreturn 1, 2
similar would be:
return *[1]
meansreturn 1
in general, splatting in a context that takes a coma separated list of items (method args, rescue, return, next, multiple assignment) is supposed to destructure the array. The array should be gone, only the elemnts remain, so returning the
[1]
is wrong.some examples
method_call *[a]
ismethod_call a
as expected
rescue *[a]
isrescue a
as expected
[*[a]]
is[a]
as expected
b = *[a]
isb = [a]
why?
(next|return) *[a]
is(next|return) [a]
why?the intuitive, expected and logical behavior would be for
*array
to consistently destructure, when used in a coma separated list context
please explain
Updated by marcandre (Marc-Andre Lafortune) over 9 years ago
bug hit wrote:
b = *[a]
isb = [a]
why?
(next|return) *[a]
is(next|return) [a]
why?
I agree, this can be surprising.
The reason for the behavior is that return 1, 2
, strictly speaking, shouldn't be valid Ruby as you can only return one value. Instead of forbidding it, return 1, 2
is automatically converted to return [1, 2]
. They are equivalent. So return *array
is converted to return [*array]
, and that holds even in the cases where array
contains one or no element (or isn't an array).
The same can be said for next
and =
.
HTH
Updated by bughit (bug hit) over 9 years ago
Marc-Andre Lafortune wrote:
bug hit wrote:
b = *[a]
isb = [a]
why?
(next|return) *[a]
is(next|return) [a]
why?I agree, this can be surprising.
The reason for the behavior is that
return 1, 2
, strictly speaking, shouldn't be valid Ruby as you can only return one value. Instead of forbidding it,return 1, 2
is automatically converted toreturn [1, 2]
. They are equivalent. Soreturn *array
is converted toreturn [*array]
, and that holds even in the cases wherearray
contains one or no element (or isn't an array).The same can be said for
next
and=
.HTH
So it seems it's an implementation artifact. Would it not be better if semantics of splatting were consistent, i.e. rvalue splat would always destructure the array?
Updated by bughit (bug hit) over 9 years ago
Nobuyoshi Nakada wrote:
It's similar to
return *[1, 2]
.
Please clarify your position, is this about preserving compatibility, or do you really disagree that conceptually an rvalue splat should eliminate the array?
Updated by najamelan (Naja Melan) over 8 years ago
bug hit wrote:
Nobuyoshi Nakada wrote:
It's similar to
return *[1, 2]
.Please clarify your position, is this about preserving compatibility, or do you really disagree that conceptually an rvalue splat should eliminate the array?
Actually this is really useful. Whenever a method accepts a something
or an array of somethings
you can use the splat operator to make it an array if it's not already. Now you can operate on it as an array.
def initialize( something )
@something = *something
end
If it didn't do that, you would have to write:
@something = something.kind_of?( Array ) ? something : [something]
Updated by bughit (bug hit) over 8 years ago
Naja Melan wrote:
Actually this is really useful. Whenever a method accepts a
something
or anarray of somethings
you can use the splat operator to make it an array if it's not already.
This is a bad hack that produces obfuscated code and does not work properly, if you splat a hash you will end up with an array of arrays. What you want is something like Array.wrap from rails, which both communicates intention clearly and works universally.
Splatting an array should consistently destructure it.
Updated by najamelan (Naja Melan) over 8 years ago
My apologies, it seems I'm mistaken. The splat operator does not just seem to coerce into array for all types:
a = { a: 1 }
p [a] # [{:a=>1}]
p *a # [:a, 1]
p [*a] # [[:a, 1]]
It seems it calls #to_a
on classes that implement this. I'm sorry if I create confusion in the ongoing discussion.
Updated by bughit (bug hit) over 8 years ago
Naja Melan wrote:
My apologies, it seems I'm mistaken. The splat operator does not just seem to coerce into array for all types:
a = { a: 1 } p [a] # [{:a=>1}] p *a # [:a, 1] p [*a] # [[:a, 1]]
It seems it calls
#to_a
on classes that implement this. I'm sorry if I create confusion in the ongoing discussion.
Also when it's already an array (a = *array), what you are asking ruby to do is first destructure the array, then coalesce it back into an array, essentially making a shallow copy, whereas what you actually want is to leave it alone. Array.wrap leaves it alone.