覆盖散列并使 [] 运算符私有 - 不能再使用 ||=

Overriding a hash and making [] operators private - cant use ||= anymore

测试代码:

class PrivHash < Hash
  def set(key, val)
     self[key] = val
  end
  def set_maybe(key, val)
    self[key] ||= val
  end
  private
  def []= key, value 
  end
  def [] key
    super            
  end
end

使用此代码,我希望 setset_maybe 都能正常工作。但是,只有 set 有效,而 set_maybe 失败:

[30] pry(#<TranslationInfo>):1> ph.set_maybe(:a, 1)
NoMethodError: private method `[]' called for {:a=>2}:#Class:0x007f99c5924c38>::PrivHash 
from (pry):56:in `set_maybe'

我假设 self[:b] ||= <x> 只是 self[:b] || self[:b] = <x> 的语法糖,但我想这不是因为它有效。

让我感到困扰的是为什么我会收到此错误。我是从 class 中执行的,所以为什么我会收到私有方法错误?

我试着反编译了它。

code = <<CODE
class PrivHash < Hash
  def set(key, val)
    self[key] = val
  end
  def set_maybe(key, val)
    self[key] ||= val
  end
  private
  def []= key, value 
  end
  def [] key
    super            
  end
end
CODE
disasm = RubyVM::InstructionSequence.compile(code).disasm
File.write("#{RUBY_VERSION}.txt", disasm)

根据结果,我得出的结论是:2.2.0 调用

0010 opt_aref         <callinfo!mid:[], argc:1, FCALL|ARGS_SIMPLE>
...
0013 branchif         25
...
0020 opt_aset         <callinfo!mid:[]=, argc:2, FCALL|ARGS_SIMPLE>

基本上,评估[],看看它是否为假,如果是,则调用[]=。但是 2.3.0 没有在 [] 调用中使用 FCALL 标志:

0010 opt_aref         <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache>
...
0014 branchif         27
...
0021 opt_aset         <callinfo!mid:[]=, argc:2, FCALL|ARGS_SIMPLE>, <callcache>

FCALL 标志标识带有隐式接收器的调用 (foo());没有 FCALL,调用是对显式接收者(self.foo()bar.foo())的调用,这是 private 方法所禁止的。

现在,为什么 2.3.0 会这样做...不知道。

I assumed that self[:b] ||= <x> is just syntactic sugar for self[:b] || self[:b] = <x>

是的。但这并不意味着它会像预处理器一样重写代码。它在 Ruby 核心代码中进行了扩展,我想这不受允许使用私有方法显式 self 的规则的约束。也许你可以再看看that feature

目前私有方法的处理有点乱。

原始规则是:

private methods can only be called without an explicit receiver.

这是一个很好、简单、易于理解的规则。它也是一个 static 规则,即它可以在没有 运行 代码的情况下进行检查,实际上,它甚至是一个 syntactic 规则,它甚至不需要复杂的静态分析,它可以在解析器中检查。

然而,很快就注意到这条规则使得调用私有 setters 变得不可能,因为 setters 不能在没有显式接收者的情况下被调用(foo = bar 是a 设置局部变量,而不是调用 setter)。因此,规则已扩展:

private methods can only be called without an explicit receiver, unless the method call is an assignment method call, in which case the method can also be called with an explicit receiver as long as that explicit receiver is the literal pseudo-variable self.

这允许您使用文字值 self:

的显式接收者调用私有 setters
self.foo = bar

但不是 self

的动态值
baz = self
baz.foo = bar # NoMethodError: private method `foo=' called

这仍然保留了 属性 可以在解析时检测到私有方法调用。

两年前,I filed a bug关于缩写方法赋值不起作用,即:

self.foo += bar # NoMethodError

通过再次扩展私有方法调用的规则修复了该错误(现在规则已经变得如此复杂以至于我不打算详细说明)。

但是,仍然有很多情况没有被现有规则涵盖,在这些情况下,方法在没有显式接收者的情况下根本无法在句法上被调用,因此不能是私有的:

self[foo]
!self
self + foo

等等

有些已经修复,有些还没有。问题是规则现在变得如此复杂以至于很难正确实施。 There have been proposals to change the rule 像这样:

private methods can only be called without an explicit receiver or an explicit receiver which is the literal pseudo-variable self.

这是一个很好、简单、易于理解的规则,可以在解析时进行静态检查,并且有 none 我们目前拥有的复杂异常和极端情况。但是,它尚未实施 AFAIK。