Be careful about Ruby method calls and variable references

Reprinted from Blog Article.

Recently, I wrote a few people who are addicted to it, so I will summarize it for a moment.

Ruby method call

In Ruby, when calling a method, you can "call the method by omitting()" compared to other languages.

def hoge(a = nil)
  "#hoge(#{a})"
end

#You can call the method without the parentheses
p hoge
# => "#hoge()"

#You can refer to the method even with a method-like call
p hoge()      # => "#hoge()"
p hoge 42     # => "#hoge(42)"
p self.hoge   # => "#hoge()"

Well this is right.

What if a variable name with the same name as the method name is defined?

The problem is when "methods and variables with the same name" are mixed. In this case, refer to "Variable if the variable can be referenced" and "Otherwise" method.

def hoge(a = nil)
  "#hoge(#{a})"
end

#Since the variable is not defined at this point, the method is called with priority.
p hoge   # => #hoge()

#Define variables
hoge = 42

#After defining the variable, give priority to the variable.
p hoge   # => 42

#In a method-like call, call the method
p hoge()      # => "#hoge()"
p hoge 42     # => "#hoge(42)"
p self.hoge   # => "#hoge()"

At this time, refer to "Method if it is before the assignment expression" and "Variable if it is after the assignment expression".

When are variables defined?

For example, in Ruby, there is a "case where the process that actually defines the variable is not called" as follows.

def hoge(a = nil)
  "#hoge(#{a})"
end

if false
  hoge = 42
end

#Is this a method reference?
p hoge

In the interpreter language, this may be expected to be "the method is called because the variable is not defined". However, in reality, p hoge refers to the" variable hoge'. This is a secret to how Ruby executes the source code. In Ruby, before executing the source code, first "parse all the Ruby source code" and then execute the Ruby code. So, like the code above, the variable hoge is implicit when the" assignment expression "is defined because all the source code is parsed regardless of" whether the code is called at runtime ". It will be defined in. Therefore, regardless of whether the contents of the if statement are actually called, when the "assignment expression" is defined, the "variable hoge "will be referenced even" outside the if statement ".

#Actual execution result
def hoge(a = nil)
  "#hoge(#{a})"
end

if false
  hoge = 42
end

#Reference variables here
#Returns nil because the default value of the variable is nil
p hoge
# nil

Postfix if + variable definition

It's very difficult to understand how Ruby parses source code. For example, what happens if you "define a hoge variable while referring to hoge with a postfix if" as follows?

hoge = 42 if hoge.nil?
p hoge

Many people think that this is because ʻif hoge.nil?Is processed before the variable definition, so the processhoge = 42is not actually called. However, in reality, the codehoge = 42 if hoge.nil? Is parsed as "this is one process in total". Therefore, when ʻif hoge.nil? Is called, the code hoge = 42 has already been parsed, and the process is that the variable hoge is defined. So, the result of executing the code above is

hoge = 42 if hoge.nil?
p hoge
# => 42

Then, " hoge = 42 "is processed. On the other hand, if it is not a postfix if, the result will be different, so you need to be careful.

#This will result in an error
# error: undefined local variable or method `hoge' for main:Object (NameError)
if hoge.nil?
  hoge = 42
end

This is because ʻif hoge.nil?` Is parsed before the variable and the conditional expression of the if statement is executed "in the state where the variable is not defined".

What happens when eval ("hoge")

In Ruby, there is a method called ʻeval`. This is a method that executes the Ruby source code "at runtime".

def hoge(a = nil)
  "#hoge(#{a})"
end

hoge = 42
#Execute the string passed to eval as Ruby code
p eval("hoge + hoge")
# => 84

When the above code is executed, `" hoge + hoge "is executed after the assignment expression, so refer to" variable ". So what happens when you run code like this:

def hoge(a = nil)
  "#hoge(#{a})"
end

#Run hoge before the assignment expression
p eval("hoge")

hoge = 42

From the flow explained earlier, "I am executing"hoge"before the variable", so I expect this to be a "method call". However, when it is actually executed, the result is as follows.

def hoge(a = nil)
  "#hoge(#{a})"
end

#Refer to a variable instead of a method call
p eval("hoge")
# => nil

hoge = 42

The reason for this result is that "the timing of Ruby source code" and "the timing of executing ʻeval" are different. The timing to execute ʻeval is" Ruby runtime ". This "runtime" is "after the Ruby source code has been parsed". In other words, since "timing to execute ʻeval" is already "after the Ruby source code has been parsed", the expression hoge refers to" variable priority ". Therefore, when referencing a variable or method from ʻeval, the "variable" is preferentially called regardless of the "definition position of the assignment expression". This also affects when using binding.irb, which is a problem when calling binding.irb "before the assignment expression", for example:

def hoge(a = nil)
  "#hoge(#{a})"
end

#Binding for debugging etc..Start irb at runtime with irb
#If you refer to hoge on this irb console, you will see "variables".
binding.irb

hoge = 42

binding.irb executes the entered Ruby code using ʻeval. Therefore, it will be executed by referring to the "variable" as in the previous example. I think it's rare to use ʻeval in normal Ruby code, but you need to be careful because using binding.irb etc. indirectly means using ʻeval`. ..

Summary

  1. In Ruby, it is ambiguous whether the expression " hoge "is a method call or a variable reference.
  2. Prefer the variable if it exists, otherwise prioritize the method call
  1. However, note that variables are prioritized when dynamically referencing variables.
  1. Basically, variable names with the same name as the method name should be avoided
  1. In Ruby, it is important to write code while being aware of whether it is a "variable" or a "method".

So, I tried to summarize the variables of Ruby. When you actually call a method with binding.irb, nil may be returned, and if you look closely at the code, a variable name with the same name is defined after binding.irb. There was a thing. As you can see, there are some addictive points in Ruby, so be careful.

Recommended Posts

Be careful about Ruby method calls and variable references
variable and method
ruby Exercise Memo II (variable and method)
About Ruby hashes and symbols
About Ruby and object model
About Ruby classes and instances
[Ruby] Questions and verification about the number of method arguments
[Ruby] present/blank method and postfix if.
About Ruby single quotes and double quotes
[Ruby basics] split method and to_s method
About Ruby product operator (&) and sum operator (|)
About object-oriented inheritance and about yield Ruby
[Ruby] About variable naming rules. Also naming conventions for constants, classes, and methods.
Be careful when omitting return in Ruby
Difference between Ruby instance variable and local variable
Be absolutely careful when putting the result of and / or in a variable!
[Ruby] How to use gsub method and sub method
[Ruby] "Reference to object" and "Contents of variable"
About call timing and arguments of addToBackStack method
About regular expressions used in ruby sub method