1

I understand that the entire line is parsed and the value of a variable is set before puts parses it in the following:

def get_value
  42
end

if value = get_value
  puts value
end
# => 42

I get the following result, which I expect:

#p = "Im totally a string" # <--Commented.
puts "Am i a string? #{p}" if p = "Im a confused string"
# => "Am i a string? "

However, this is weird. Having p declared prior to the one-liner changes the output in an unexpected way:

p = "foo" # <--Un-commented.
puts "Am i a string? #{p}" if p = "Im a confused string"
# => "Am i a string? Im a confused string"

p is a FixNum, not String in the following:

p = 1
puts "Am i a string? #{p}" if p = "Im a confused string"
# => "Am i a string? Im a confused string"

What is going on? If its not obvious at first, the second block of code illustrates how "Im a confused string" fails to be interpolated. However, in the third example, simply having p be declared (agnostic of type) results in "Im a confused string" being interpolated

I believe that this question is different from, but similar to, these questions:

Community
  • 1
  • 1
  • 3
    In using `p =` instead of `p ==` you are _assigning_ to `p` instead of checking equality, which returns the assigned value to `if`, and that value is always truthy for a string (or anything other than `false, nil`. – Michael Berkowski Mar 19 '15 at 02:44
  • Just was I was starting to type. LOL Yes, Ruby allows you to use the success of a method as a condition. In this case the method is `=`. – Beartech Mar 19 '15 at 02:46
  • you can always call .class on a variable to check what kind of class it is. @MichaelBerkowski called it though...you're using = instead of == – hummmingbear Mar 19 '15 at 03:38
  • what is your question? – Nafaa Boutefer Mar 19 '15 at 05:44
  • 1
    Ok - whomever edited the question totally changed the context of what I was asking and makes me look like a moron. Im sorry for not being clear: I understand the use of = vs ==. Whomever edited the post un-commented the `#p="Im totally a string"` in the second code block i provided. The issue is that the result of the identical lines `puts "Am i a string? #{p}" if p = "Im a confused string"` changes when `p` is declared beforehand. The be concise: Having the variable `p` declared at all (FixNum/String etc.) changes the output of `puts "Am i a string? #{p}" if p = "Im a confused string"` – William Dembinski Mar 19 '15 at 06:55
  • Edited the post to better reflect the original question. – William Dembinski Mar 19 '15 at 07:10
  • Which ruby version is this? – Sergio Tulentsev Mar 19 '15 at 07:41
  • Thank you for your amazing replies. The ruby version info :ruby 2.2.1p85 (2015-02-26 revision 49769) [x86_64-linux] – William Dembinski Mar 19 '15 at 09:33
  • Thanks for restoring your question, it actually makes sense now, and I've deleted my answer because it's no longer relevant. – smathy Mar 19 '15 at 15:48

4 Answers4

1

I'm not getting the same results that you are... (older ruby, however)

irb(main):001 > puts "#{variable_heaven}" if variable_heaven = "pizza"
NameError: undefined local variable or method `variable_heaven' for main:Object

irb(main):002 > p variable_heaven
=> "pizza"

According to the first stack overflow answer you referenced, in this example of trailing conditional the parser assumes variable_heaven ... having not been encountered to this point ... is a method call.

The rule of thumb to derive is...

assignments in conditionals are always executed prior to the conditioned code being executed (true for leading conditionals (of course) and for trailing conditionals)

However... referencing undefined variables in trailing conditionals is problematic, even if establishing the variable with assignment in the conditional.

The code that you "expected" is anomalous: in normal behaviour assignments and/or methods in trailing conditionals are executed before the conditioned code, so if the conditioned code references a variable with contents modified by the conditional, the conditioned code will be using the variable as modified.

SteveTurczyn
  • 36,057
  • 6
  • 41
  • 53
1

TLDR: Unfortunately, your examples are flawed, because you have chosen a name for your variable that clashes with existing method from core ruby.


As @SteveTurczyn mentioned a few minutes ago, if variable is not known before the line with the conditional, it's interpreted as a method call.

Let's explore some machine code, shall we? Important lines are commented.

puts "Am i a string? #{myvar}" if myvar = "Im a confused string"

== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1)
[ 2] myvar      
0000 trace            1                                               (   2)
0002 putstring        "Im a confused string"
0004 dup              
0005 setlocal_OP__WC__0 2
0007 branchunless     22
0009 putself          
0010 putobject        "Am i a string? "
0012 putself          
0013 opt_send_simple  <callinfo!mid:myvar, argc:0, FCALL|VCALL|ARGS_SKIP> # call method myvar
0015 tostring         
0016 concatstrings    2
0018 opt_send_simple  <callinfo!mid:puts, argc:1, FCALL|ARGS_SKIP>
0020 leave            
0021 pop              
0022 putnil           
0023 leave            

And when variable is declared beforehands

myvar = "Im totally a string"
puts "Am i a string? #{myvar}" if myvar = "Im a confused string"

== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1)
[ 2] myvar      
0000 trace            1                                               (   1)
0002 putstring        "Im totally a string"
0004 setlocal_OP__WC__0 2
0006 trace            1                                               (   2)
0008 putstring        "Im a confused string"
0010 dup              
0011 setlocal_OP__WC__0 2
0013 branchunless     27
0015 putself          
0016 putobject        "Am i a string? "
0018 getlocal_OP__WC__0 2 # Read variable value
0020 tostring         
0021 concatstrings    2
0023 opt_send_simple  <callinfo!mid:puts, argc:1, FCALL|ARGS_SKIP>
0025 leave            
0026 pop              
0027 putnil           
0028 leave            

Now, the problem with your code is that p is a method that exists. In case you didn't know about it, p foo is equivalent to puts foo.inspect. Similarly to puts, it accepts flexible number of arguments (even zero arguments) and returns nil.

puts "Am i a string? #{p}" if p = "Im a confused string"
                       ^ call method `p` here

But

p = "foo" # Shadow existing method `p`
puts "Am i a string? #{p}" if p = "Im a confused string"
                       ^ get local var
                         if you wanted to also call the method `p`, you'd have to just through some extra hoops
                         or just rename the variable. 
Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
1

p is a method defined in Kernel. Here's a example with an "unused" name and an explicit method definition:

def x
  123
end

puts "x is a #{defined?(x)} with value #{x}" if x = 'foo'
#=> x is a method with value 123

This is due to Ruby's parse order. Ruby parses the puts expression first and recognizes x as a method. It then executes the if expression and assigns a local variable x. When evaluating the puts part afterwards, x still refers to the method. (see Modifier if and unless for details)

On the other hand:

x = 123

puts "x is a #{defined?(x)} with value #{x}" if x = 'foo'
#=> x is a local-variable with value foo

Here, x is a local variable from the beginning.

Note that the local variable x exists in both cases:

def x
  123
end

puts "#{send(:x)} #{binding.local_variable_get(:x)}" if x = 'foo'
#=> 123 foo
Stefan
  • 109,145
  • 14
  • 143
  • 218
0

Changing the variable from p stopped the anomaly.

Sergio Tulentsev's response was "correct"!