Ruby: Why symbols change to strings when using puts instead of print? - ruby

I don't understand the behavior of print and puts?
I know print would not make a new line but puts can.
but why the output of print will change from symbol to string when using puts instead of print?
`$ ruby -e 'print Kernel.private_instance_methods(false)'
[:initialize_copy, :remove_instance_variable, :sprintf, :format, :Integer, :Float, :String, :Array, :warn, :raise, :fail, :global_variables, :__method__, :__callee__, :eval, :local_variables, :iterator?, :block_given?, :catch, :throw, :loop, :caller, :trace_var`
$ ruby -e 'puts Kernel.private_instance_methods(false)'
initialize_copy
remove_instance_variable
sprintf
format
Integer
Float
String
Array
warn
raise
fail
global_variables
__method__
__callee__
eval
local_variables

When you call puts, what really gets called is the rb_io_puts C function, which basically works like this:
If there is no argument, output a newline.
For each argument check if it's of type string (T_STRING in Ruby C lingo) and if yes, call rb_io_write with it. Also, if the string was of length zero or didn't finish in a newline, add a \n.
If the argument is an array, recursively call io_puts_ary on it.
In any other case, call rb_obj_as_string on the argument, which basically is the low-level equivalent of to_s.
So when you puts [:a, :b, :c], you'll hit the third case and io_puts_ary will take over. Long story short this will do something similar as what I described above, and will call rb_obj_as_string on each element and output it followed by a newline.

The print function will call array's to_s function,
the array's to_s function is the alias of inspect function.
This can be found in ruby's array.c code.
rb_define_alias(rb_cArray, "to_s", "inspect");
Therefore:
array = Kernel.private_instance_methods(false)
$stout.write(array.to_s)
will also output the same result.

Related

Multiple Ruby chomp! statements

I'm writing a simple method to detect and strip tags from text strings. Given this input string:
{{foobar}}
The function has to return
foobar
I thought I could just chain multiple chomp! methods, like so:
"{{foobar}}".chomp!("{{").chomp!("}}")
but this won't work, because the first chomp! returns a NilClass. I can do it with regular chomp statements, but I'm really looking for a one-line solution.
The String class documentation says that chomp! returns a Str if modifications have been made - therefore, the second chomp! should work. It doesn't, however. I'm at a loss at what's happening here.
For the purposes of this question, you can assume that the input string is always a tag which begins and ends with double curly braces.
You can definitely chain multiple chomp statements (the non-bang version), still having a one-line solution as you wanted:
"{{foobar}}".chomp("{{").chomp("}}")
However, it will not work as expected because both chomp! and chomp removes the separator only from the end of the string, not from the beginning.
You can use sub
"{{foobar}}".sub(/{{(.+)}}/, '\1')
# => "foobar"
"alfa {{foobar}} beta".sub(/{{(.+)}}/, '\1')
# => "alfa foobar beta"
# more restrictive
"{{foobar}}".sub(/^{{(.+)}}$/, '\1')
# => "foobar"
Testing this out, it's clear that chomp! will return nil if the separator it's provided as an argument is not present at the end of the string.
So "{{text}}".chomp!("}}") returns a string, but "{{text}}".chomp!("{{") reurns nil.
See here for an answer of how to chomp at the beginning of a string. But recognize that chomp only looks at the end of the string. So you can call str.reverse.chomp!("{{").reverse to remove the opening brackets.
You could also use a regex:
string = "{{text}}"
puts [/^\{\{(.+)\}\}$/, 1]
# => "text"
Try tr:
'{{foobar}}'.tr('{{', '').tr('}}', '')
You can also use gsub or sub but if the replacement is not needed as pattern, then tr should be faster.
If there are always curly braces, then you can just slice the string:
'{{foobar}}'[2...-2]
If you plan to make a method which returns the string without curly braces then DO NOT use bang versions. Modifying the input parameter of a method will be suprising!
def strip(string)
string.tr!('{{', '').tr!('}}', '')
end
a = '{{foobar}}'
b = strip(a)
puts b #=> foobar
puts a #=> foobar

How to use ":template"-type strings in ruby?

Both rails routes and whenever and a few other things I can't remember have a user-specified template string like so:
template_str = "I am going to :place at :time"
And then there's some magic function which embeds data in place of :place and :time, like so:
template_str.magic_embed_function(place: 'bed', time: '10 pm')
#=> "I am going to bed at 10 pm"
How can I do this in my ruby projects? Is there a gem that implements String#magic_embed_function?
Use Percent-Style Interpolation
There is a special type of interpolation that uses the String#% method. This allows you to interpolate ordinal (Array) and non-ordinal (Hash) inputs into a format string similar to that provided by Kernel#sprintf. However, the use of a hash argument with this method enables support for named variables in the format string. As a minimalist example:
"%{foo} %{bar}" % {foo: 'baz', bar: 'quux'}
#=> "baz quux"
With a hash argument, the format-string placeholders are treated as hash keys to be replaced by the associated values in your hash. This makes the order of the variables passed in unimportant. Based on the code in your original post, you could use it as follows:
template_str = 'I am going to %{place} at %{time}.'
template_str % {time: '10:00 PM', place: 'bed'}
#=> "I am going to bed at 10:00 PM."
This is a useful technique when you want to pass an array or hash for interpolation, but may or may not offer advantages over other types of interpolation in the general case. Your mileage may vary.
I extended String class with a magic_embed_function as you asked..rs
It's very simple, first we split our string and collect the words and check if matches with this simple regex for symbols, basically says "if something starts with : , that's a symbol", after we found a symbol we replace using gsub! (global substitution, with the bang to change our object) passing our symbol as first param and the param received that corresponds to that symbol and at the end we return self, to return the string that called the method.
template_str = "I am goind to :place at :time"
class String
def magic_embed_function(params)
self.split(" ").collect do |value|
if value =~ /:.*/
self.gsub! value, params[value[1..value.length].to_sym]
end
end
self
end
end
p template_str.magic_embed_function({place: "bed", time: "10 pm"})
#"I am goind to bed at 10 pm"

Why do parentheses affect hashes?

When I used respond_with and passed a literal hash, it gave me the error:
syntax error, unexpected tASSOC, expecting '}'
`respond_with {:status => "Not found"}`
However, when I enclosed the literal hash in parentheses like so:
respond_with({:status => "Not found"})
the function runs without a hitch. Why do the parentheses make a difference? Isn't a hash an enclosed call?
When calling a method, the opening curly bracket directly after the method name is interpreted as the start of a block. This has precedence over the interpretation as a hash. One way to circumvent the issue is to use parenthesis to enforce the interpretation as a method argument. As an example, please note the difference in meaning of these two method calls:
# interpreted as a block
[:a, :b, :c].each { |x| puts x }
# interpreted as a hash
{:a => :b}.merge({:c => :d})
Another way is to just get rid of the curly brackets as you can always skip the brackets on the last argument of a method. Ruby is "clever" enough to interpret everything which looks like an association list at the end of an argument list as a single hash. Please have a look at this example:
def foo(a, b)
puts a.inspect
puts b.inspect
end
foo "hello", :this => "is", :a => "hash"
# prints this:
# "hello"
# {:this=>"is", :a=>"hash"}

Ruby 1.9 prints strings twice

class Test
def printsomething
p "lol"
end
end
teet = Test.new
p "#{teet.printsomething}"
Output for above code is "lol"\n"lol"
why is this happening? I am running ruby 1.9.2 Archlinux x86_64
p is an inspect not really meant to be used to output text string. What it does is prints out the literal content of an object not an escaped string.
Just replace p with puts
You can see what I mean if you do this:
p "#{teet}"
=> "#<Test:0x00000100850678>"
Notice how it's inside quotes.
First thing that Ruby does when it sees a double-quoted string is replacing the #{expr} parts with the result of evaluated expr. For example, "#{2+2}" becomes "4". So, let's see what happens here. Ruby evaluates teet.printsomething. During this evaluation it executes the method and it prints "lol" in the 3-rd line. Note that although the method printsomething doesn't have a return statement, it nevertheless returns some value: it's the value returned by the last statement of that method. The return value of p object is the object itself, so the result of the method printsomething is "lol". This result now replaces the #{} part in the string, and the string becomes "lol" instead of "#{teet.printsomething}". Now the p method in the 7-th line is executed and outputs "lol" again.
What happens if you replace p with puts? The difference is that the return value of puts is nil. When the result of expr is nil, the whole expression #{} is replaced by empty string. So the 7-th line becomes puts "". As a result, the whole program outputs "lol" followed by an empty line.

Why does ruby output values like this?

Fairly new to ruby, can someone explain why these 2 things respond differently?
a=["A","B","C"]
puts a
A
B
C
puts "#{a}"
ABC
a.to_s returns the same output as the templating output, but shouldn't the simple "puts a" do the same?
The specified behavior of puts is that it writes stuff out with a newline afterwards. If it's an array, it writes each element with a newline.
When you do puts a.to_s, it does the to_s first (resulting in a single string) and then outputs that single string with a newline afterward.
As discussed in this thread, and for no good reason, Arrays have magically inconsistent behavior when given to puts.
array.each {|e| puts e }
is the same as:
puts array

Resources