shorthand 像 ruby 中的 (+=) 这样的运算符到底有什么作用?
What does shorthand operator like (+=) in ruby really does?
虽然我可以理解 x += 1
等同于 x = x + 1
,但我对它在后台的作用很感兴趣。
我在 irb 中尝试过 x += 1
,但没有先给 x 赋值,我肯定得到一个错误,它说
NoMethodError (undefined method `+' for nil:NilClass)
当我在出错后尝试检查 x 值时,它现在是一个 nil 值,它在分配时不太清楚,除非它是由 x += 1
语句完成的。
所以,我试图了解 += 是如何做的,因为我找不到它的文档,或者它被埋在 ruby 类 的后面。任何指针将不胜感激。
Ruby 如果看到赋值 (=
) 则将标识符视为变量,即使赋值失败也是如此。
irb(main):001:0> a
NameError: undefined local variable or method `a' for main:Object
from (irb):1
from /usr/bin/irb:12:in `<main>'
irb(main):002:0> a = 1 if false
=> nil
irb(main):003:0> a
=> nil
irb(main):004:0>
即使 a=1
从不执行,a
也会默认为 nil
。
分析
定义我们的条款
首先,+
和+=
是方法,不是keywords or operators. More accurately, + is defined as Integer#+,而+=
是解释器提供的语法糖,作为shorthand递增赋值等常见操作。
其次,+=
及相关操作在Ruby中调用abbreviated assignments。当前支持的作业 shorthand 项的完整列表包括:
["+=",
"-=",
"*=",
"/=",
"%=",
"**=",
"&=",
"|=",
"^=",
"<<=",
">>=",
"||=",
"&&="]
Ruby中唯一的non-method operators是:
["=", "..", "...", "!", "not", "&&", "and", "||", "or", "!=", "!~"]
和 shorthand 赋值运算符,如 +=
不是用户可定义的。
定义问题
您看到的行为与作用域以及解析器如何处理块局部变量有关。具体来说,Ruby assignment documentation 表示:
[A] local variable is created when the parser encounters the assignment, not when the assignment occurs[.]
这意味着顶级代码如:
x
#=> NameError (undefined local variable or method `x' for main:Object)
引发 NameError 异常,因为 defined? x #=> nil
。但是,解析器会在分配时自动实例化局部变量,以便以下工作(某种程度上),至少在它不会引发 NameError:
的意义上
# x is autovivified as nil
x = x + 1
#=> NoMethodError (undefined method `+' for nil:NilClass)
但是,nil 没有 NilClass#+ 方法,因此您得到的是 NoMethodError 而不是 NameError。这是因为:
nil.respond_to? :+
#=> false
解决方案
基本解决方案是明确确保您的变量存在于当前范围内,并且它不是 nil。您可以通过多种方式执行此操作,但最简单的方法就是直接分配它。例如:
# assign zero to x unless it's truthy
x ||= 0
# perform incrementing assignment
x += 1
或者,您可以删除 shorthand 并将自动生成的 x 转换为整数, 有一个 :+
方法。例如,假设 x 当前未定义(例如,您已重新启动 irb,或者已将 x 设置为 nil):
x = x.to_i + 1
#=> 1
虽然我可以理解 x += 1
等同于 x = x + 1
,但我对它在后台的作用很感兴趣。
我在 irb 中尝试过 x += 1
,但没有先给 x 赋值,我肯定得到一个错误,它说
NoMethodError (undefined method `+' for nil:NilClass)
当我在出错后尝试检查 x 值时,它现在是一个 nil 值,它在分配时不太清楚,除非它是由 x += 1
语句完成的。
所以,我试图了解 += 是如何做的,因为我找不到它的文档,或者它被埋在 ruby 类 的后面。任何指针将不胜感激。
Ruby 如果看到赋值 (=
) 则将标识符视为变量,即使赋值失败也是如此。
irb(main):001:0> a
NameError: undefined local variable or method `a' for main:Object
from (irb):1
from /usr/bin/irb:12:in `<main>'
irb(main):002:0> a = 1 if false
=> nil
irb(main):003:0> a
=> nil
irb(main):004:0>
即使 a=1
从不执行,a
也会默认为 nil
。
分析
定义我们的条款
首先,+
和+=
是方法,不是keywords or operators. More accurately, + is defined as Integer#+,而+=
是解释器提供的语法糖,作为shorthand递增赋值等常见操作。
其次,+=
及相关操作在Ruby中调用abbreviated assignments。当前支持的作业 shorthand 项的完整列表包括:
["+=",
"-=",
"*=",
"/=",
"%=",
"**=",
"&=",
"|=",
"^=",
"<<=",
">>=",
"||=",
"&&="]
Ruby中唯一的non-method operators是:
["=", "..", "...", "!", "not", "&&", "and", "||", "or", "!=", "!~"]
和 shorthand 赋值运算符,如 +=
不是用户可定义的。
定义问题
您看到的行为与作用域以及解析器如何处理块局部变量有关。具体来说,Ruby assignment documentation 表示:
[A] local variable is created when the parser encounters the assignment, not when the assignment occurs[.]
这意味着顶级代码如:
x
#=> NameError (undefined local variable or method `x' for main:Object)
引发 NameError 异常,因为 defined? x #=> nil
。但是,解析器会在分配时自动实例化局部变量,以便以下工作(某种程度上),至少在它不会引发 NameError:
# x is autovivified as nil
x = x + 1
#=> NoMethodError (undefined method `+' for nil:NilClass)
但是,nil 没有 NilClass#+ 方法,因此您得到的是 NoMethodError 而不是 NameError。这是因为:
nil.respond_to? :+
#=> false
解决方案
基本解决方案是明确确保您的变量存在于当前范围内,并且它不是 nil。您可以通过多种方式执行此操作,但最简单的方法就是直接分配它。例如:
# assign zero to x unless it's truthy
x ||= 0
# perform incrementing assignment
x += 1
或者,您可以删除 shorthand 并将自动生成的 x 转换为整数, 有一个 :+
方法。例如,假设 x 当前未定义(例如,您已重新启动 irb,或者已将 x 设置为 nil):
x = x.to_i + 1
#=> 1