测试代码:
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
使用此代码,我希望set
和set_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>
的语法糖,但我想这不是因为这有效。
我有什么问题就是为什么我收到这个错误..我在课堂上执行这个,所以为什么我会收到私有方法错误?
答案 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。