84

I am just starting to learn Ruby (first time programming), and have a basic syntactical question with regards to variables, and various ways of writing code.

Chris Pine's "Learn to Program" taught me to write a basic program like this...

num_cars_again= 2
puts 'I own ' + num_cars_again.to_s + ' cars.'

This is fine, but then I stumbled across the tutorial on ruby.learncodethehardway.com, and was taught to write the same exact program like this...

num_cars= 2
puts "I own #{num_cars} cars."

They both output the same thing, but obviously option 2 is a much shorter way to do it.

Is there any particular reason why I should use one format over the other?

leemeichin
  • 3,339
  • 1
  • 24
  • 31
Jeff H.
  • 923
  • 1
  • 7
  • 9
  • 6
    Ugh. I hate how often beginners books teach you an un-natural way of doing things without at least telling you that alternatives exist. +1 for a legitimate question that hasn't been upvoted. – Andrew Grimm Apr 09 '12 at 23:47
  • There are more options that are discussed at http://stackoverflow.com/questions/377768/string-concatenation-and-ruby – sameers Jun 26 '13 at 21:00

5 Answers5

78

Whenever TIMTOWTDI (there is more than one way to do it), you should look for the pros and cons. Using "string interpolation" (the second) instead of "string concatenation" (the first):

Pros:

  • Is less typing
  • Automatically calls to_s for you
  • More idiomatic within the Ruby community
  • Faster to accomplish during runtime

Cons:

  • Automatically calls to_s for you (maybe you thought you had a string, and the to_s representation is not what you wanted, and hides the fact that it wasn't a string)
  • Requires you to use " to delimit your string instead of ' (perhaps you have a habit of using ', or you previously typed a string using that and only later needed to use string interpolation)
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • 25
    Don't forget the "it's faster" aspect. String concatenation, in this example, has to create 3 strings in total, while string interpolation only creates one. – Dominik Honnef Apr 09 '12 at 16:51
  • 4
    if you care about benchmarks it's also faster: [try me in REPL](https://gist.github.com/1704455) – leemeichin Apr 09 '12 at 16:51
  • 2
    Thanks so much for the answers. Quick question. Why would Chris Pine's book teach the longer way to do it? Maybe it is better for a beginner to learn doing it the longer way? His book says most the time lazier=better, so I'm wondering if maybe for some reason (since I'm just learning), I should continue doing it his way or move forward with this better way. Any ideas? – Jeff H. Apr 09 '12 at 16:52
  • 6
    My guess: because _"attaching strings together using a well-known operator"_ is a simpler concept for a new programmer than _"use a custom syntax to evaluate arbitrary code, call `to_s` on the result, and inject it into the middle of a string"_. When learning any new thing there are often variations on "the simple-to-understand way" versus "the way the professionals do it". – Phrogz Apr 09 '12 at 16:54
  • What about the `<<` append operator? If performance is your concern isn't that the best option? I tried testing the append operator inside of the benchmark @leemachin linked to and it was faster than both concatenation and interpolation. I realize that this is a concatenation + interpolation comparison, but if were talking about what's available the append operator is another choice. – Noz Feb 06 '13 at 22:35
  • I just wanted to chime in and say: @DominikHonnef you do realize that in that string it's casting more strings when you interpolate? and not only that, interpolation can result in more messages than concatenation. The it's faster or creates less strings argument is a fallacy here. This is nothing more than a preference. – Jordon Bedwell Oct 02 '13 at 11:16
  • 7
    I know I'm super-late to this discussion, but there are two main reasons why I did it the way that I did. First, for the reasons Phrogz gives: I was trying to keep it as simple as possible, using concepts they already knew. I didn't even cover double-quoted strings in the first edition! The last thing someone wants when learning to program is six different syntaxes for creating strings. Second, because of the implicit `to_s`. Yes, for those of us who understand types and conversions, it's a convenience. But for a new programmer, it's really important to understand those concepts. – Chris Pine Sep 09 '14 at 03:30
  • Does `#{}` call `to_s` or `to_str`? I'm having trouble finding documentation about that. – YoTengoUnLCD Jul 19 '16 at 04:22
  • @YoTengoUnLCD Easy enough for you to find out. Create a new class with both methods, with different return strings, and see which gets used in interpolation. – Phrogz Jul 19 '16 at 06:00
  • There are more cons: It increases cognitive load by requiring you to know about more cases where escaping in the string is necessary and requiring you to know a second (mostly redundant) set of syntax for using variables. – B T Nov 05 '18 at 19:31
9

Both interpolation and concatination has its own strength and weakness. Below I gave a benchmark which clearly demonstrates where to use concatination and where to use interpolation.

require 'benchmark'

iterations = 1_00_000
firstname = 'soundarapandian'
middlename = 'rathinasamy'
lastname = 'arumugam'

puts 'With dynamic new strings'
puts '===================================================='
5.times do
  Benchmark.bm(10) do |benchmark|
    benchmark.report('concatination') do
      iterations.times do
        'Mr. ' + firstname + middlename + lastname + ' aka soundar'
      end
    end

    benchmark.report('interpolaton') do
      iterations.times do
        "Mr. #{firstname} #{middlename} #{lastname} aka soundar"
      end
    end
  end
  puts '--------------------------------------------------'
end

puts 'With predefined strings'
puts '===================================================='
5.times do
  Benchmark.bm(10) do |benchmark|
    benchmark.report('concatination') do
      iterations.times do
        firstname + middlename + lastname
      end
    end

    benchmark.report('interpolaton') do
      iterations.times do
        "#{firstname} #{middlename} #{lastname}"
      end
    end
  end
  puts '--------------------------------------------------'
end

And below is the Benchmark result

Without predefined strings
====================================================
                 user     system      total        real
concatination  0.170000   0.000000   0.170000 (  0.165821)
interpolaton  0.130000   0.010000   0.140000 (  0.133665)
--------------------------------------------------
                 user     system      total        real
concatination  0.180000   0.000000   0.180000 (  0.180410)
interpolaton  0.120000   0.000000   0.120000 (  0.125051)
--------------------------------------------------
                 user     system      total        real
concatination  0.140000   0.000000   0.140000 (  0.134256)
interpolaton  0.110000   0.000000   0.110000 (  0.111427)
--------------------------------------------------
                 user     system      total        real
concatination  0.130000   0.000000   0.130000 (  0.132047)
interpolaton  0.120000   0.000000   0.120000 (  0.120443)
--------------------------------------------------
                 user     system      total        real
concatination  0.170000   0.000000   0.170000 (  0.170394)
interpolaton  0.150000   0.000000   0.150000 (  0.149601)
--------------------------------------------------
With predefined strings
====================================================
                 user     system      total        real
concatination  0.070000   0.000000   0.070000 (  0.067735)
interpolaton  0.100000   0.000000   0.100000 (  0.099335)
--------------------------------------------------
                 user     system      total        real
concatination  0.060000   0.000000   0.060000 (  0.061955)
interpolaton  0.130000   0.000000   0.130000 (  0.127011)
--------------------------------------------------
                 user     system      total        real
concatination  0.090000   0.000000   0.090000 (  0.092136)
interpolaton  0.110000   0.000000   0.110000 (  0.110224)
--------------------------------------------------
                 user     system      total        real
concatination  0.080000   0.000000   0.080000 (  0.077587)
interpolaton  0.110000   0.000000   0.110000 (  0.112975)
--------------------------------------------------
                 user     system      total        real
concatination  0.090000   0.000000   0.090000 (  0.088154)
interpolaton  0.140000   0.000000   0.140000 (  0.135349)
--------------------------------------------------

Conclusion

If strings already defined and sure they will never be nil use concatination else use interpolation.Use appropriate one which will result in better performance than one which is easy to indent.

Soundar Rathinasamy
  • 6,658
  • 6
  • 29
  • 47
  • which Ruby version did you use? – La-comadreja Dec 09 '14 at 21:13
  • 1
    I tried it in ruby 2.5.0 and the `interpolation` is faster than `concatenation` in both cases. I couldn't paste results here because of comment's length limit but you can try it yourself. – Peter T. Mar 12 '18 at 09:23
  • 1
    I'm not sure this is totally fair, in that `"#{firstname} #{middlename} #{lastname}"` should probably be compared to `firstname + " " + middlename + " " + lastname`, not `firstname + middlename + lastname` (concat on 5 strings vs. 3 strings) – Mickalot Feb 17 '20 at 19:30
  • 1
    Note that when I change your benchmark to make them comparable (by either removing the inner spaces in `"#{firstname} #{middlename} #{lastname}"` or adding spaces to the `concatination` cases), interpolation is *always* significantly faster (at least, using Ruby 2.6.3 on Mac OSX). – Mickalot Feb 17 '20 at 19:35
4

@user1181898 - IMHO, it's because it's easier to see what's happening. To @Phrogz's point, string interpolation automatically calls the to_s for you. As a beginner, you need to see what's happening "under the hood" so that you learn the concept as opposed to just learning by rote.

Think of it like learning mathematics. You learn the "long" way in order to understand the concepts so that you can take shortcuts once you actually know what you are doing. I speak from experience b/c I'm not that advanced in Ruby yet, but I've made enough mistakes to advise people on what not to do. Hope this helps.

Martin Graham
  • 116
  • 1
  • 7
3

If you are using a string as a buffer, I found that using concatenation (String#concat) to be faster.

require 'benchmark/ips'

puts "Ruby #{RUBY_VERSION} at #{Time.now}"
puts

firstname = 'soundarapandian'
middlename = 'rathinasamy'
lastname = 'arumugam'

Benchmark.ips do |x|
    x.report("String\#<<") do |i|
        buffer = String.new

        while (i -= 1) > 0
            buffer << 'Mr. ' << firstname << middlename << lastname << ' aka soundar'
        end
    end

    x.report("String interpolate") do |i|
        buffer = String.new

        while (i -= 1) > 0
            buffer << "Mr. #{firstname} #{middlename} #{lastname} aka soundar"
        end
    end

    x.compare!
end

Results:

Ruby 2.3.1 at 2016-11-15 15:03:57 +1300

Warming up --------------------------------------
           String#<<   230.615k i/100ms
  String interpolate   234.274k i/100ms
Calculating -------------------------------------
           String#<<      2.345M (± 7.2%) i/s -     11.761M in   5.041164s
  String interpolate      1.242M (± 5.4%) i/s -      6.325M in   5.108324s

Comparison:
           String#<<:  2344530.4 i/s
  String interpolate:  1241784.9 i/s - 1.89x  slower

At a guess, I'd say that interpolation generates a temporary string which is why it's slower.

ioquatix
  • 1,411
  • 17
  • 32
0

Here is a full benchmark which also compares Kernel#format and String#+ as it's all methods for construction dynamic string in ruby that I know

require 'benchmark/ips'

firstname = 'soundarapandian'
middlename = 'rathinasamy'
lastname = 'arumugam'

FORMAT_STR = 'Mr. %<firstname>s %<middlename>s %<lastname>s aka soundar'
Benchmark.ips do |x|
  x.report("String\#<<") do |i|
    str = String.new
    str << 'Mr. ' << firstname << ' ' << middlename << ' ' << lastname << ' aka soundar'
  end

  x.report "String\#+" do
    'Mr. ' + firstname + ' ' + middlename + ' ' + lastname + ' aka soundar'
  end

  x.report "format" do
    format(FORMAT_STR, firstname: firstname, middlename: middlename, lastname: lastname)
  end

  x.report("String interpolate") do |i|
    "Mr. #{firstname} #{middlename} #{lastname} aka soundar"
  end

  x.compare!
end

And results for ruby 2.6.5

Warming up --------------------------------------
           String#<<
    94.597k i/100ms
            String#+    75.512k i/100ms
              format    73.269k i/100ms
  String interpolate   164.005k i/100ms
Calculating -------------------------------------
           String#<<     91.385B (±16.9%) i/s -    315.981B
            String#+    905.389k (± 4.2%) i/s -      4.531M in   5.013725s
              format    865.746k (± 4.5%) i/s -      4.323M in   5.004103s
  String interpolate    161.694B (±11.3%) i/s -    503.542B

Comparison:
  String interpolate: 161693621120.0 i/s
           String#<<: 91385051886.2 i/s - 1.77x  slower
            String#+:   905388.7 i/s - 178590.27x  slower
              format:   865745.8 i/s - 186768.00x  slower
mpospelov
  • 1,510
  • 1
  • 15
  • 24