Splat, #to_ary and #to_a

E

Eero Saynatkari

Hi!

I suppose this just qualifies as unexpected (by me) behaviour
but an odd thing happened when I wrote code like this:

[*"foo\nbar"] # => ["foo\n", "bar"]

Mocking out the call sequence for the splat, it seems that
it first checks for #respond_to? :to_ary and, if so, sends
it to the object--the surprising part was that if it does
NOT respond to :to_ary, the splat simply calls #to_a on its
subject.

The idea behind the code, of course, is to have a single
expression that either constructs an Array or reproduces
the current one from whatever object we are dealing with.
This behaviour makes doing so a tad less concise :)
 
E

Eero Saynatkari

Eero said:
Hi!

I suppose this just qualifies as unexpected (by me) behaviour
but an odd thing happened when I wrote code like this:

[*"foo\nbar"] # => ["foo\n", "bar"]

On the same topic, this is a strange parsing:

stream.puts ([] << obj).join("\n")

Above is the same as writing:

stream.puts([] << obj).join("\n")
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: Splat, #to_ary and #to_a"

|I suppose this just qualifies as unexpected (by me) behaviour
|but an odd thing happened when I wrote code like this:
|
| [*"foo\nbar"] # => ["foo\n", "bar"]

It's fixed in 1.9. 1.8 will remain as it is now for the sake of
compatibility.

matz.
 
E

Eero Saynatkari

Yukihiro said:
Hi,

In message "Re: Splat, #to_ary and #to_a"
on Mon, 18 Sep 2006 11:12:43 +0900, Eero Saynatkari

|I suppose this just qualifies as unexpected (by me) behaviour
|but an odd thing happened when I wrote code like this:
|
| [*"foo\nbar"] # => ["foo\n", "bar"]

It's fixed in 1.9. 1.8 will remain as it is now for the sake of
compatibility.

Thank you for clarification!
 
R

Rick DeNatale

Hi,

In message "Re: Splat, #to_ary and #to_a"

|I suppose this just qualifies as unexpected (by me) behaviour
|but an odd thing happened when I wrote code like this:
|
| [*"foo\nbar"] # => ["foo\n", "bar"]

It's fixed in 1.9. 1.8 will remain as it is now for the sake of
compatibility.

Hmmmm,

Just what is the fix in 1.9.

Are you saying that
[*"foo\nbar"] #=> ["foo\nbar"]
in 1.9?

How about:
[*(1..4)]
which in 1.8.x produces [1, 2, 3, 4]

and which of these will change in 1.9?

def a(*arg)
p arg
end

a("foo\nbar")
which in 1.8.4 prints ["foo\nbar"]

a((1..3))
prints [1, 2, 3, 4]
a(1..3)
prints [1..3]


a(1..3, "foo\nbar")
prints [1..3, "foo\nbar"]

a(*"foo\nbar")
prints ["foo\n", "bar"]

a(*(1..4))
prints [1, 2, 3, 4]


*a = "foo\nbar"
a gets ["foo\nbar"]
*a = (1..4)
a gets [1..4]
*a = "foo\nbar", (1..3)
a gets ["foo\nbar", 1..3]

I'm not sure that I see a consistent pattern here. But I have to say
that I think that the 1.8.x interpretations of

a(*"foo\nbar")
and
a(*(1..4))

both make sense since the caller is ASKING for the splat, and that
these two seem very similar to the
[*"foo\nbar"]
case which started this.


I've also always been surprised that String#to_a didn't produce an
array of single character strings, instead of splitting the string
into lines, but that's probably just a personal surprise, and I
wouldn't expect that to change.
 
S

Simen Edvardsen

Hi!

I suppose this just qualifies as unexpected (by me) behaviour
but an odd thing happened when I wrote code like this:

[*"foo\nbar"] # => ["foo\n", "bar"]

Mocking out the call sequence for the splat, it seems that
it first checks for #respond_to? :to_ary and, if so, sends
it to the object--the surprising part was that if it does
NOT respond to :to_ary, the splat simply calls #to_a on its
subject.

The idea behind the code, of course, is to have a single
expression that either constructs an Array or reproduces
the current one from whatever object we are dealing with.
This behaviour makes doing so a tad less concise :)

Can't you just test whether the object is an Array (or if it supports
the array methods you need)?

x = "foo\nbar"
(x.is_a?(Array) ? x : [x]) # => ["foo\nbar"]
x = %w(a b c)
(x.is_a?(Array) ? x : [x]) # => ["a", "b", "c"]

 
Y

Yukihiro Matsumoto

Hi,

In message "Re: Splat, #to_ary and #to_a"

|Just what is the fix in 1.9.
|
|Are you saying that
|[*"foo\nbar"] #=> ["foo\nbar"]
|in 1.9?

Yes. And if you are curious you can try it by yourself.

|How about:
|[*(1..4)]
|which in 1.8.x produces [1, 2, 3, 4]

Currently,

[1..4]

This may be an issue.

|and which of these will change in 1.9?
|
|def a(*arg)
| p arg
|end
|
|a("foo\nbar")
|which in 1.8.4 prints ["foo\nbar"]

["foo\nbar"]

|a((1..3))
| prints [1, 2, 3, 4]

[1..3]

|a(1..3)
| prints [1..3]

[1..3]

|a(1..3, "foo\nbar")
|prints [1..3, "foo\nbar"]

[1..3, "foo\nbar"]

|a(*"foo\nbar")
|prints ["foo\n", "bar"]

["foo\nbar"]

|a(*(1..4))
|prints [1, 2, 3, 4]

[1..4]

| *a = "foo\nbar"
| a gets ["foo\nbar"]

["foo\nbar"]

| *a = (1..4)
| a gets [1..4]

[1..4]

| *a = "foo\nbar", (1..3)
| a gets ["foo\nbar", 1..3]

["foo\nbar", 1..3]

matz.
 
D

dblack

Hi --

Hi,

In message "Re: Splat, #to_ary and #to_a"
on Mon, 18 Sep 2006 12:28:18 +0900, "Rick DeNatale"

|Just what is the fix in 1.9.
|
|Are you saying that
|[*"foo\nbar"] #=> ["foo\nbar"]
|in 1.9?

Yes. And if you are curious you can try it by yourself.

|How about:
|[*(1..4)]
|which in 1.8.x produces [1, 2, 3, 4]

Currently,

[1..4]

This may be an issue.

I've always thought it was convenient to have [*x] do the expanding
on x (including strings and ranges). But if it's going to disappear,
I think it should disappear consistently, so that * doesn't start
having a totally different meaning depending on the operand's class.


David

--
David A. Black | (e-mail address removed)
Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] http://www.manning.com/black | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org
 
M

Marcelo Alvim

and which of these will change in 1.9?

def a(*arg)
p arg
end

a("foo\nbar")
which in 1.8.4 prints ["foo\nbar"]

a((1..3))
prints [1, 2, 3, 4]

Does the following happen only to me?

irb(main):001:0> def a(*arg)
irb(main):002:1> p arg
irb(main):003:1> end
=> nil
irb(main):004:0> a((1..3))
[1..3]
=> nil

Using ruby 1.8.4 (2006-04-14) [i386-mswin32].

Thanks,
Alvim.
 
D

Daniel Schierbeck

Marcelo said:
and which of these will change in 1.9?

def a(*arg)
p arg
end

a("foo\nbar")
which in 1.8.4 prints ["foo\nbar"]

a((1..3))
prints [1, 2, 3, 4]

Does the following happen only to me?

irb(main):001:0> def a(*arg)
irb(main):002:1> p arg
irb(main):003:1> end
=> nil
irb(main):004:0> a((1..3))
[1..3]
=> nil

Using ruby 1.8.4 (2006-04-14) [i386-mswin32].

No, it the way it's supposed to be.

a (1..3) #=> outputs: [1..3]
a *(1..3) #=> outputs: [1, 2, 3]


Cheers,
Daniel
 
E

Eero Saynatkari

unknown said:
Hi --

|in 1.9?

This may be an issue.

I've always thought it was convenient to have [*x] do the expanding
on x (including strings and ranges). But if it's going to disappear,
I think it should disappear consistently, so that * doesn't start
having a totally different meaning depending on the operand's class.

I dunno--you have to use Range#to_a explicitly elsewhere so I
feel that would be consistent here also. I think my preferred
solution would be for splat to use #to_ary and leave everything
else explicit.

Of course this is a very limited application of the splat operator.
 
D

dblack

Hi --

unknown said:
Hi --

|in 1.9?

This may be an issue.

I've always thought it was convenient to have [*x] do the expanding
on x (including strings and ranges). But if it's going to disappear,
I think it should disappear consistently, so that * doesn't start
having a totally different meaning depending on the operand's class.

I dunno--you have to use Range#to_a explicitly elsewhere so I
feel that would be consistent here also.

My point is that I don't want to have to use Range#to_a explicitly but
not String#to_a, nor the other way around. In other words, I don't
want *(0...10) to do one thing and *"a\nb" to do another. It's not
that I'm hung up on consistency per se (lots of cool Ruby things come
from inconsistency :) but I don't like things where there are two
cases and no clear reason why, so you have to memorize them
individually.


David

--
David A. Black | (e-mail address removed)
Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] http://www.manning.com/black | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org
 
R

Rick DeNatale

=== A reply to my questions ===

Thanks Matz!

Summarizing Matz' reply to my use cases:

1.9 here means the version of 1.9 as of Matz' reply:

Use case

Splat formal parameter.

def a(*arg)
p arg
end


a("foo\nbar")
1.8 ["foo\nbar"]
1.9 ["foo\nbar"]

a((1..3))
1.8 [1..3] Note: I think I had a typo on this one in my original post.
1.9 [1..3]

a(1..3)
1.8 [1..3]
1.9 [1..3]

a(1..3, "foo\nbar")
1.8 [1..3, "foo\nbar"]
1.9 [1..3, "foo\nbar"]

So here, once my typo is accounted for, there's no difference.

Parallel assignment
*a = "foo\nbar"
1.8 ["foo\nbar"]
1.9 ["foo\nbar"]
*a = (1..4)
1.8 [1..4]
1.8 [1..4]
*a = "foo\nbar", (1..3)
1.8 ["foo\nbar", 1..3]
1.9 ["foo\nbar", 1..3]

Again no changes here.

Splat's in array literal:
[*"foo\nbar"]
1.8 ["foo\n", "bar"]
1.9 ["foo\nbar"]

[*(1..4)]
1.8 [1, 2, 3, 4]
1.9 [1..4]

Here the difference seems to be that in 1.8 the splat had an effect
but in 1.9 it doesn't, at least for strings and ranges. Personally
this seems like the wrong direction to me, but that's a gut reaction.

The reason I THINK that it's wrong is that it looks like it seems to
ignore the explicit request represented by the *. Now I can
understand that it might be confusing in a case like this:

def b(str)
[*str]
end

b("a") #=> ["a"]
b("foo\nbar") #=> ["foo\n", "bar"]

But I'm not convinced that it's a net positive change. Convincable,
but not convinced.

method call with splat argument

a(*"foo\nbar")
1.8 ["foo\n", "bar"]
1.9 ["foo\nbar"]

a(*(1..4))
1.8 [1, 2, 3, 4]
1.9 [1..4]

Here again, it looks like an explicit request to splat the parameter
is being ignored in 1.9.

Note that this very case came up recently in a discussion of expanding
an array in the arguments to Array#values_at like the method does
itself for range arguments.

Sorry I haven't installed 1.9 myself, so I'm wondering if something like

(1..10).to_a.values_at(3..5)

still works the same way as it does in 1.8 (returning [4, 5, 6]) or
throws an exception saying that it can't convert a range to an integer
similar to the way 1.8 does with:
(1..10).to_a.values_at([3, 4, 5])
 
M

MonkeeSage

Rick said:
Sorry I haven't installed 1.9 myself, so I'm wondering if something like

(1..10).to_a.values_at(3..5)

still works the same way as it does in 1.8 (returning [4, 5, 6]) or
throws an exception saying that it can't convert a range to an integer
similar to the way 1.8 does with:
(1..10).to_a.values_at([3, 4, 5])

$ ruby19 -v && ruby19 -e "p (1..10).to_a.values_at(3..5)"
ruby 1.9.0 (2006-09-19) [i686-linux]
[4, 5, 6]

Regards,
Jordan
 
R

Rick DeNatale

$ ruby19 -v && ruby19 -e "p (1..10).to_a.values_at(3..5)"
ruby 1.9.0 (2006-09-19) [i686-linux]
[4, 5, 6]

Regards,
Jordan

Thanks, I guess I'm going to have to find the time to install 1.9.

Those darn clients take up too much time with their problems! And in
this case it's problems with a big wad o'php I just inherited. A sorry
mess which is a prime candidate for takeover by assimilation by ruby
code, once I understand enough of what it's supposed to do.
 
M

MonkeeSage

Just for posterity, it may be worth pointing what Eero hinted at. If
you add a to_ary method to Range / String in 1.9, then you get the old
behavior for the use cases that show differences (e.g., [*(1..10)] ):

class Range
def to_ary
self.inject([]) { |ary, item| ary << item }
end
end

Regards,
Jordan
 
M

MonkeeSage

Rick said:
Those darn clients take up too much time with their problems! And in
this case it's problems with a big wad o'php I just inherited. A sorry
mess which is a prime candidate for takeover by assimilation by ruby
code, once I understand enough of what it's supposed to do.

"Resistance is futile; we will take your knowledge and add it to the
collective. You will be assimilated" hehe! ;)

Regards,
Jordan
 
R

Rick DeNatale

Just for posterity, it may be worth pointing what Eero hinted at. If
you add a to_ary method to Range / String in 1.9, then you get the old
behavior for the use cases that show differences (e.g., [*(1..10)] ):

Yes, I somehow missed the TITLE of this thread!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,154
Members
46,702
Latest member
LukasConde

Latest Threads

Top