2016-05-10 6 views
6

テストコードカント使用||:このコードではハッシュをオーバーライドして、[]演算子はプライベート作る - =もう

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>のためだけ糖衣構文ですが、私はこれが動作するため、それがない推測と仮定しました。

なぜ私はこのエラーが発生しているのですか。私はクラス内からこれを実行しています。なぜプライベートメソッドエラーが発生しますか?

+1

私はこのエラーメッセージ(Ruby 2.2.4)を取得しませんでした。どのRubyバージョンを使用していますか? – user1934428

+0

2.3.0のエラーを取得しています。 – Amadan

+0

私は実際にルビー2.3の下で同じ動作を観察します。 '|| ='は失敗しますが、 'self [key] || self [key] = val'バージョンが動作します。 Rubyの下2.2 *両方のバージョンが動作します。どうやら、 '|| ='は他のバージョンの文法的な砂糖だけではありません。 – BoraMa

答えて

3

現在、プライベートメソッドの処理はちょっと混乱しています。

ルールは:

プライベートメソッドは、明示的なレシーバなしで呼び出すことができます。

これは簡単で分かりやすいルールです。 スタティックルールです。つまり、コードを実行せずにチェックすることができます。実際には構文ルールであっても、洗練された静的解析は必要なく、パーサでチェックすることもできます。

しかし、明示的な受信者なしではsetterを呼び出すことができないので、このルールはプライベートセッターを呼び出すことができなくなることに気付きました(foo = barはセッターを呼び出さないローカル変数を設定しています)。エルゴは、ルールは、拡張された:

プライベートメソッドは、明示的な受信せずに呼び出すことができ、メソッド呼び出し、メソッドも限り、明示的な受信機と呼ばれることができ、その場合に割り当てメソッドの呼び出しがある場合を除きその明示的な受信者は、文字の擬似変数selfです。

これはリテラル値selfの明示的な受信機と民間のセッターを呼び出すことができます:

self.foo = bar 

なくself

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

の動的な値。これは、まだそのプロパティを保持しますパース時にプライベートメソッド呼び出しを検出できます。

2年前、略し方法の割り当てについてI filed a bugはすなわち、動作していない:バグが再びプライベートメソッド呼び出しのためのルールを拡張する(そして今のルールが既にI非常に複雑になっていることにより、固定された

self.foo += bar # NoMethodError 

私はそれを綴りません)。

self[foo] 
!self 
self + foo 

など

しかし、これらの方法は、単に文法的にプライベートにすることはできませんので、明示的なレシーバなしで呼び出さとすることができない既存のルールによってカバーされないままに例がたくさん残っています

一部は修正されていますが、修正されていないものもあります。問題はルールが複雑になり、正しく実装することが難しいことです。このようなものにThere have been proposals to change the rule

プライベートメソッドは、明示的な受信機またはリテラル擬似変数selfで明示的なレシーバなしで呼び出すことができます。

これは、わかりやすく、わかりやすいルールです。パース時に静的にチェックすることができ、複雑な例外やコーナーケースはありません。しかし、AFAIKはまだ実装されていません。

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) 

、私は問題がこのさ結論:それはfalsyだかどうかを確認し、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なしでは、privateメソッドで禁止されている明示的な受信者(self.foo()またはbar.foo())への呼び出しです。

なぜ、2.3.0がこれを行うのですか...わかりません。

+0

バグのようです。 – Stefan

+0

私は1.9.3を使用していますので、必ずしも2.3バグではありません。 –

+0

@ KarthikT:2.3.0または2.1.1(私のテストで)または1.9.3(あなたの)、それは2.2.4(EDIT:user1934428による)または2.2.4とJRuby 1.7.11で動作します。非常に矛盾しています...おそらく、古いバグが2.2で押しつぶされ、2.3で復活しましたか? – Amadan

2

私はself[:b] ||= <x>はい、それはあるself[:b] || self[:b] = <x>

のためだけ糖衣構文であると仮定しました。しかし、それは、プリプロセッサのように、コードをその場所に書き換えるという意味ではありません。これはRubyのコアコード内で展開されており、プライベートメソッドの明示的なselfを許可するルールの対象ではないと思います。おそらくthat featureをもう一度見ることができます。

+0

ああ興味深い...しかし私は自己と1.9.3で私的な方法を行うことができます。それはその機能よりも古いものです。 –

+1

Good find、@sawa。 OP、その機能要求では、当時の(そしておそらくは現在の)コードベースで、プライベートライターとオペレータに関して処理されなければならない例外について話します。 – Amadan

関連する問題