Ruby safe navigation operator 是否在 receiver 为 nil 时评估其参数?
Does Ruby safe navigation operator evaluate its parameters when its receiver is nil?
问题:
Ruby 安全导航运算符 (&.
) 是否在其接收器为 nil
时评估其参数?
例如:
logger&.log("Something important happened...")
"Something important happened..."
字符串是否在此处计算?
- 您能否提供一个权威来源,证明或否认这一事实?
- 或建议一种检查方法?
提前致谢。
为什么我要寻找这个问题的答案?
我的代码库中有如下代码:
logger.log("Something important happened. (#{Time.current})") if verbose
我的主要目标是删除每次调用 log
方法时重复的 if verbose
检查,因为它很容易被遗忘,而且您根本不会收到有关误用的通知.
受Tell, don't ask原则的启发,
我已经移动了 if verbose
检查内部 log
方法实现。
class Logger
# ...
def log(message)
return unless verbose
# ...
end
end
def logger
@logger ||= Logger.new
end
logger.log("Something important happened. (#{Time.current})")
这种方法简化了我的代码,因为我已经解决了我的主要问题 - 我不需要记住在每次调用 log
方法时放置 if verbose
,
但我又收到了一个问题。
"Something important..."
字符串总是被评估,无论 verbose
是 true
还是 false
.
因此,我彻底改变了解决方案:
logger
returns nil
当 verbose
为 false
.
- Ruby 安全导航运算符应在
log
调用前使用。
def logger
@logger ||= Logger.new if verbose
end
logger&.log("Something important happened. (#{Time.current})")
因此,我已将最初记住 if verbose
检查的问题替换为记住 &.
调用。
但是,无论如何,我认为这是一个改进,因为忘记使用安全导航运算符会引发 NoMethodError
,换句话说,通知 log
方法误用。
所以现在,为了确保 'safe navigation operator approach' 实际上是我的问题的 'better' 选项,
我需要确切地知道 Ruby 中的安全导航运算符是否在其接收器为 nil
时评估其参数。
它不评估它们:
require 'pry'
logger = nil
logger&.log(binding.pry)
这个returns:
nil
如果它对其进行评估,那么它会像这个示例那样触发绑定:
a = []
a&.push(binding.pry)
如果你没有撬但有现代版本的 Ruby,你可以用 binding.irb
代替 binding.pry
。
这是否是一个“更好”的解决方案是您应该确定的基准。
您可以在 How is the Ruby safe navigation (&.) implemented?
阅读有关安全导航运算符的更多信息
没有,而且很容易测试:
$ irb
> def test
> puts 'triggered!'
> end
=> :test
> def nothing
> end
=> :nothing
> nothing&.whatever(test)
=> nil
> nothing&.whatever("string_#{test}")
=> nil
从概念上讲,您可能会认为安全导航运算符是这样的:
x&.test(param) # is "conceptually" equal to
if x.respond_to?(:test)
x.test(param)
end
# or, as pointed in the comment:
unless x.nil?
x.test(param)
end
现在很清楚为什么在未调用时不对其求值了。
引自the syntax documentation安全导航运营商:
&.
, called “safe navigation operator”, allows to skip method call when receiver is nil
. It returns nil
and doesn't evaluate method's arguments if the call is skipped.
因此,如果 logger
是 nil
,当您将它称为
时,您的 log
方法的参数不会被评估
logger&.log("something happened at #{Time.now}")
话虽如此,请注意 Ruby 核心记录器针对您的确切问题提供了不同的解决方案,即避免在日志级别过高时不得不评估潜在的昂贵参数。
Ruby 核心记录器实现其 add
方法,类似于这样(简化):
class Logger
attr_accessor :level
def initialize(level)
@level = level.to_i
end
def add(severity, message = nil)
return unless severity >= level
message ||= yield
log_device.write(message)
end
def info(message = nil, &block)
add(1, message, &block)
end
end
然后您可以将其用作
logger = Logger.new(1)
logger.info { "something happened at #{Time.now}" }
这里,只有当日志级别足够高以至于消息被实际使用时,才会评估该块。
表达式已解析但未执行
logger&.log
的参数在 logger.is_a?(NilClass) == true
时未计算。评估的每个 Ruby 表达式都应该产生影响,因此请考虑:
test = 1
nil&.log(test+=1); test
#=> 1
如果参数由解释器求值,test 将等于 2。因此,虽然解析器肯定 解析 参数中的表达式,但它不会 执行 内部表达式。
您可以通过 Ripper#sexp:
验证解析器看到的内容
require 'ripper'
test = 1
pp Ripper.sexp "nil&.log(test+=1)"; test
[:program,
[[:method_add_arg,
[:call,
[:var_ref, [:@kw, "nil", [1, 0]]],
[:@op, "&.", [1, 3]],
[:@ident, "log", [1, 5]]],
[:arg_paren,
[:args_add_block,
[[:opassign,
[:var_field, [:@ident, "test", [1, 9]]],
[:@op, "+=", [1, 13]],
[:@int, "1", [1, 15]]]],
false]]]]]
#=> 1
这清楚地表明解析器在符号表达式树中看到了递增的赋值。但是,分配从未真正执行过。
问题:
Ruby 安全导航运算符 (&.
) 是否在其接收器为 nil
时评估其参数?
例如:
logger&.log("Something important happened...")
"Something important happened..."
字符串是否在此处计算?- 您能否提供一个权威来源,证明或否认这一事实?
- 或建议一种检查方法?
提前致谢。
为什么我要寻找这个问题的答案?
我的代码库中有如下代码:
logger.log("Something important happened. (#{Time.current})") if verbose
我的主要目标是删除每次调用 log
方法时重复的 if verbose
检查,因为它很容易被遗忘,而且您根本不会收到有关误用的通知.
受Tell, don't ask原则的启发,
我已经移动了 if verbose
检查内部 log
方法实现。
class Logger
# ...
def log(message)
return unless verbose
# ...
end
end
def logger
@logger ||= Logger.new
end
logger.log("Something important happened. (#{Time.current})")
这种方法简化了我的代码,因为我已经解决了我的主要问题 - 我不需要记住在每次调用 log
方法时放置 if verbose
,
但我又收到了一个问题。
"Something important..."
字符串总是被评估,无论 verbose
是 true
还是 false
.
因此,我彻底改变了解决方案:
logger
returnsnil
当verbose
为false
.- Ruby 安全导航运算符应在
log
调用前使用。
def logger
@logger ||= Logger.new if verbose
end
logger&.log("Something important happened. (#{Time.current})")
因此,我已将最初记住 if verbose
检查的问题替换为记住 &.
调用。
但是,无论如何,我认为这是一个改进,因为忘记使用安全导航运算符会引发 NoMethodError
,换句话说,通知 log
方法误用。
所以现在,为了确保 'safe navigation operator approach' 实际上是我的问题的 'better' 选项,
我需要确切地知道 Ruby 中的安全导航运算符是否在其接收器为 nil
时评估其参数。
它不评估它们:
require 'pry'
logger = nil
logger&.log(binding.pry)
这个returns:
nil
如果它对其进行评估,那么它会像这个示例那样触发绑定:
a = []
a&.push(binding.pry)
如果你没有撬但有现代版本的 Ruby,你可以用 binding.irb
代替 binding.pry
。
这是否是一个“更好”的解决方案是您应该确定的基准。
您可以在 How is the Ruby safe navigation (&.) implemented?
阅读有关安全导航运算符的更多信息没有,而且很容易测试:
$ irb
> def test
> puts 'triggered!'
> end
=> :test
> def nothing
> end
=> :nothing
> nothing&.whatever(test)
=> nil
> nothing&.whatever("string_#{test}")
=> nil
从概念上讲,您可能会认为安全导航运算符是这样的:
x&.test(param) # is "conceptually" equal to
if x.respond_to?(:test)
x.test(param)
end
# or, as pointed in the comment:
unless x.nil?
x.test(param)
end
现在很清楚为什么在未调用时不对其求值了。
引自the syntax documentation安全导航运营商:
&.
, called “safe navigation operator”, allows to skip method call when receiver isnil
. It returnsnil
and doesn't evaluate method's arguments if the call is skipped.
因此,如果 logger
是 nil
,当您将它称为
log
方法的参数不会被评估
logger&.log("something happened at #{Time.now}")
话虽如此,请注意 Ruby 核心记录器针对您的确切问题提供了不同的解决方案,即避免在日志级别过高时不得不评估潜在的昂贵参数。
Ruby 核心记录器实现其 add
方法,类似于这样(简化):
class Logger
attr_accessor :level
def initialize(level)
@level = level.to_i
end
def add(severity, message = nil)
return unless severity >= level
message ||= yield
log_device.write(message)
end
def info(message = nil, &block)
add(1, message, &block)
end
end
然后您可以将其用作
logger = Logger.new(1)
logger.info { "something happened at #{Time.now}" }
这里,只有当日志级别足够高以至于消息被实际使用时,才会评估该块。
表达式已解析但未执行
logger&.log
的参数在 logger.is_a?(NilClass) == true
时未计算。评估的每个 Ruby 表达式都应该产生影响,因此请考虑:
test = 1
nil&.log(test+=1); test
#=> 1
如果参数由解释器求值,test 将等于 2。因此,虽然解析器肯定 解析 参数中的表达式,但它不会 执行 内部表达式。
您可以通过 Ripper#sexp:
验证解析器看到的内容require 'ripper'
test = 1
pp Ripper.sexp "nil&.log(test+=1)"; test
[:program, [[:method_add_arg, [:call, [:var_ref, [:@kw, "nil", [1, 0]]], [:@op, "&.", [1, 3]], [:@ident, "log", [1, 5]]], [:arg_paren, [:args_add_block, [[:opassign, [:var_field, [:@ident, "test", [1, 9]]], [:@op, "+=", [1, 13]], [:@int, "1", [1, 15]]]], false]]]]] #=> 1
这清楚地表明解析器在符号表达式树中看到了递增的赋值。但是,分配从未真正执行过。