Consider function f,
def f(a)
return a = 2 if !a.nil?
return 'oh'
end
f(42) # 2
f(nil) # 'oh'
And consider function g,
def g(b)
return a = b if !a.nil?
return 'oh'
end
g(42) # 'oh'
g(nil) # 'oh'
And consider function h,
def h(b)
a = b
return a if !a.nil?
return 'oh'
end
h(42) # 42
h(nil) # 'oh'
I expected g(42) to return 42 ? Why does g(42) not return 42 ?
What is the order of evaluation here that is the difference between f and g, and between g and h?
CodePudding user response:
return a = b if !a.nil?
return 'oh'
is mostly equivalent to
if !a.nil?
return a = b
end
return 'oh'
As such, Ruby first tests whether a is not nil (which is false because a is in fact nil there as it had not been assigned a value yet). Because of that, the body of the if is not executed and the execution follows along to the return 'oh'.
The more important question here is however: why did this work at all and did not result in an error such as
NameError: undefined local variable or method `a'
when trying to access the a variable in the if, even though it was not initialized before.
This works because Ruby initializes variables with nil if they appear on the left-hand side of an assignment in the code, even though the variable may not actually be assigned. This behavior is further explained in e.g. https://stackoverflow.com/a/12928261/421705.
Following this logic, your code thus only works with your original inline-if but would fail with the block-form if as with this longer form, a would only be initialized within the if block. Here, you would thus get the NoMethodError.
CodePudding user response:
It is a matter of lexical parsing as @HolgerJust pointed out.
There are some other similarly interesting side effects of using the modifier-[if/unless]
def a; 1; end;
(a if a = true) == a
#=> false
Here's how the parser sees it in a nutshell:
- Define a method
a() - The parser then encounters
aas part of the then body so it tags thisaas a method call (a()) becauseais not a local variable at this point and the ruby syntax allows for omission of parentheses in method calls. - The parser then encounters the test expression and here it marks
aas a local variable, due to the assignment (=) - The test expression is executed and in process it assigns
athe value oftrueand the test passes - The then body is now executed which calls the
a()method, because this is how the referenceawas identified in #2, which causes this expression(a if a = true)to return1. - However as pointed out in #4 the assignment to
ahas also occurred so this comparison becomes(1) == true
Note: If you remove the method definition this will raise a NameError because of #2 however the local variable assignment will still occur.
begin
c if c = 1
rescue NameError
puts 'Oh'
c
end == c and c == 1
# 'Oh'
#=> true
