Ruby 1.9 Array.To_S Behaves Differently

Ruby 1.9 Array.to_s behaves differently?

Yes, you're calling to_s on an array of strings. In 1.8 that is equivalent to calling join, in 1.9 it is equivalent to calling inspect.

To get the behavior you want in both 1.8 and 1.9, call join instead of to_s.

Difference in `Array#to_s` in Ruby 1.8 and Ruby 1.9

Here's what is actually in Ruby source code:

1.8.7:

rb_ary_to_s(ary)
VALUE ary;
{
if (RARRAY(ary)->len == 0) return rb_str_new(0, 0);
return rb_ary_join(ary, rb_output_fs);
}

In other words, in 1.8.7, to_s calls join.

1.9.3:

rb_ary_inspect(VALUE ary)
{
if (RARRAY_LEN(ary) == 0) return rb_usascii_str_new2("[]");
return rb_exec_recursive(inspect_ary, ary, 0);
}

VALUE
rb_ary_to_s(VALUE ary)
{
return rb_ary_inspect(ary);
}

In other words, in 1.9.3, to_s delegates to inspect.

Note: in future, if you're wondering about a difference you're observing between two versions, you can try taking a look at source code. Easy to pull down from here: https://github.com/ruby/ruby

Not everything is going to be easy to find in there of course, but if you search around for a bit you can often find good leads. In this case, array.c has what you're looking for.

Then you can switch back and forth between versions by issuing git checkout ruby_1_8_7 and git checkout ruby_1_9_3.

Getting custom .to_s working in arrays

Override inspect

class Custom
def inspect
'custom thing'
end
end

c1 = Custom.new
c2 = Custom.new
c3 = Custom.new
[c1,c2,c3].to_s # => "[custom thing, custom thing, custom thing]"

Not getting correct output from to_s

The examples work using ruby 1.8.7, which is getting a bit dated. Ruby 1.9.3 (the current version) changed the to_s implementation for Arrays and Hashes.

EDIT: See Ruby 1.9 Array.to_s behaves differently?

Why do this Ruby object have both to_s and inspect methods that appear to do the same thing?

inspect is used more for debugging and to_s for end-user or display purposes.

For example, [1,2,3].to_s and [1,2,3].inspect produce different output.

times function's values changes outside the loop

An easy/simple way to do this I think is as below:

array = []
4.times do |e|
e = e + 1
array << e
end
p array.join(',')

output: "1,2,3,4"

The above will print all the items separated by a comma, also this might be helpful join docs

Ruby Array#puts not using overridden implementation?

From the Ruby Programming Language:

alt text http://ecx.images-amazon.com/images/I/41n-JSlBHkL._SL75_.jpg

Output streams are appendable, like strings and arrays are, and you can write values to them with the << operator. puts is one of the most common output methods. It converts each of its arguments to a string, and writes each one to the stream. If the string does not already end with a newline character, it adds one. If any of the arguments to puts is an array, the array is recursively expanded, and each element is printed on its own line as if it were passed directly as an argument to puts. The print method converts its arguments to strings, and outputs them to the stream. If the global field separator $, has been changed from its default value of nil, then that value is output between each of the arguments to print. If the output record separator $/ has been changed from its default value of nil, then that value is output after all arguments are printed.

As for design decisions, that I do not know.

Why does Ruby include? behave differently when nested within an if/end conditional?

Here's what's happening in each of these examples:

First Example

This example outputs 1. Your string includes a j or J. regardless of the previous line. The my_string.include? check is being ignored as it's not used in a comparison anywhere, so the second line is just a regular puts.

Second Example

The second example is a little more interesting. ("j" or "J") is syntax in Ruby which will output the first of the provided arguments which evaluates to true. "j" evaluates to true because it's not nil or false, so it becomes the argument of the second include? method. include? is case-sensitive, so it will return false – the string Jack does not include a lowercase j.

You can try this out by running irb and entering something like 1 or 2 or false and 1; you'll see pretty quickly that the first true argument is returned (or false if no arguments are true).

There's no good way to make this work as-is, other than updating the include? check to use something like set intersections. An easier solution may be to downcase the input before checking characters.

Avdi Grimm posted a good video on using and and or in Ruby.

Third Example

The third example is calling include? twice on the string, and returning true when it hits the second call, hence the if statement being evaluated.

Update

papirtiger's answer got me thinking, so I did a bit of digging with Ripper using the following script:

require 'ripper'
require 'pp'

expression = <<-FOO
if true
puts 'Hello'
end
FOO

pp Ripper.sexp(expression)

Here's the result:

[:program,
[[:if,
[:var_ref, [:@kw, "true", [1, 3]]],
[[:command,
[:@ident, "puts", [2, 2]],
[:args_add_block,
[[:string_literal,
[:string_content, [:@tstring_content, "Hello", [2, 8]]]]],
false]]],
nil]]]

After updating the expression to the following:

expression = <<-FOO
if
true
puts 'Hello'
end
FOO

This was the new output:

[:program,
[[:if,
[:var_ref, [:@kw, "true", [2, 2]]],
[[:command,
[:@ident, "puts", [3, 2]],
[:args_add_block,
[[:string_literal,
[:string_content, [:@tstring_content, "Hello", [3, 8]]]]],
false]]],
nil]]]

It looks as though Ruby does indeed ignore any whitespace and evaluate the next expression. I don't have enough expertise to dig much deeper, but after trying a few more examples (such as throwing a dozen newlines in after an if statement), I'm convinced.



Related Topics



Leave a reply



Submit