1

Just encountered some strange behaviour while using string interpolation to print an object and his value after being passed to a function.

print "#{obj} => #{function obj}"

It outputed something similar both for #{obj} and #{function obj}

I came to the conclusion that it was because my function was a mutating function. I can reproduce the output easily with :

def mutate_the_object(obj)
  obj.concat '1'
end
obj = '1'
print "#{obj} != #{mutate_the_object obj} != #{mutate_the_object obj} != #{obj}"

=> 111 != 111 != 111 != 111

Why does the string interpolation give 4 equal fields? I thought the result would be something like :

=> 1 != 11 != 111 != 111

7
  • Obviously, because the string interpolation has the lowest precedence ever. Commented Apr 13, 2018 at 12:09
  • @mudasobwa yes I did no think of that, in my mind it had to work like it would in C using printf("%d %d %d",i,++i,++i); thanks Commented Apr 13, 2018 at 12:20
  • 1
    @AlexisDelahaye it’s like "%s != %s != %s != %s" % [o, m(o), m(o), o]. First the RHO array is prepared (both calls to m(o) are done,) then the string is being interpolated. Commented Apr 13, 2018 at 12:42
  • 4
    All arguments in RHO are the very same object. def m(o); o << '1'; end; o = '1'; [o, m(o), m(o), o].map(&:__id__) #⇒ [46935826870120, 46935826870120, 46935826870120, 46935826870120]. That is the same object. Commented Apr 13, 2018 at 12:47
  • 1
    TL;DR: mutation is a base ground of 99% of bugs and/or unmaintainable code. Commented Apr 13, 2018 at 12:48

2 Answers 2

1

String#concat changes the object and returns it.

def mutate_the_object changes the object it receives as argument and returns it.

It seems the interpolated expressions are computed first, then their values are used to compute the string, in a similar fashion the C function sprintf() works.

The code posted in the question is equivalent to:

def mutate_the_object(obj)
  obj.concat '1'
end
obj = '1'
obj1 = mutate_the_object obj
obj2 = mutate_the_object obj
print "#{obj} != #{obj1} != #{obj2} != #{obj}"

obj1 and obj2 are variables that hold references to the same object as obj.

This can be easily proved by running:

print <<END
    #{obj.__id__}
    #{(mutate_the_object obj).__id__}
    #{(mutate_the_object obj).__id__}
    #{obj.__id__}
END
Sign up to request clarification or add additional context in comments.

Comments

1

I looked into the ISO Ruby Language Specification, the RubySpec, and the RDoc for interpolated String literals, and neither of the three define a specific evaluation order for interpolations. They probably should explicitly say that evaluation-order is undefined, but still, the fact that it isn't explicitly defined, means that it is implicitly undefined.

So, in other words: if your interpolations have side-effects, the order of those side-effects both with respect to each other and the time when they are inserted into the string is unspecified, and what you are observing is a perfectly legal implementation of the spec.

From what I can tell, (at least) any of the following results is legal:

'1 != 11 != 111 != 1'
'1 != 11 != 111 != 11'
'1 != 11 != 111 != 111'
'1 != 111 != 11 != 1'
'1 != 111 != 11 != 11'
'1 != 111 != 11 != 111'
'1 != 111 != 111 != 1'
'1 != 111 != 111 != 11'
'1 != 111 != 111 != 111'
'11 != 11 != 111 != 1'
'11 != 11 != 111 != 11'
'11 != 11 != 111 != 111'
'11 != 111 != 11 != 1'
'11 != 111 != 11 != 11'
'11 != 111 != 11 != 111'
'11 != 111 != 111 != 1'
'11 != 111 != 111 != 11'
'11 != 111 != 111 != 111'
'111 != 11 != 111 != 1'
'111 != 11 != 111 != 11'
'111 != 11 != 111 != 111'
'111 != 111 != 11 != 1'
'111 != 111 != 11 != 11'
'111 != 111 != 11 != 111'
'111 != 111 != 111 != 1'
'111 != 111 != 111 != 11'
'111 != 111 != 111 != 111'

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.