覆盖哈希并将[]运算符设为私有 - 不能再使用|| =

时间:2016-05-10 05:13:42

标签: ruby operators override

测试代码:

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>的语法糖,但我想这不是因为这有效。

我有什么问题就是为什么我收到这个错误..我在课堂上执行这个,所以为什么我会收到私有方法错误?

3 个答案:

答案 0 :(得分:3)

我试图反编译它。

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这样做......不知道。

答案 1 :(得分:3)

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

原始规则是:

  

私有方法只能在没有显式接收器的情况下调用。

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

然而,很快就注意到这条规则使得无法调用私有setter,因为没有显式接收器就无法调用setter(foo = bar是设置局部变量,而不是调用setter)。因此,规则得到了扩展:

  除非方法调用是赋值方法调用,否则只能在没有显式接收器的情况下调用私有方法,在这种情况下,只要显式接收器是文字伪变量,也可以使用显式接收器调用该方法{ {1}}。

这允许您使用文字值self的显式接收者调用私有的setter:

self

但不是self.foo = bar

的动态值
self

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

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

baz = self
baz.foo = bar # NoMethodError: private method `foo=' called

通过再次扩展私有方法调用的规则来修复该错误(现在规则已经变得如此复杂,以至于我不会拼出它)。

但是,仍有很多案例未被现有规则所涵盖,其中方法在没有显式接收器的情况下根本无法在语法上调用,因此不能是私有的:

self.foo += bar # NoMethodError

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

  

私有方法只能在没有显式接收器或显式接收器的情况下调用,该接收器是文字伪变量self[foo] !self self + foo

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

答案 2 :(得分:2)

  

我认为self[:b] ||= <x>只是self[:b] || self[:b] = <x>

的语法糖

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