2016-11-15 73 views
1

我正在嘗試編寫helper_method類型的功能。在這樣做時,我遇到了這種奇怪的行爲:爲不需要參數的方法散列散列時的不同行爲

irb(main):001:0> def a; end 
=> :a 
irb(main):002:0> b = {} 
=> {} 
irb(main):003:0> a(**{}) 
=> nil 
irb(main):004:0> a(**b) 
ArgumentError: wrong number of arguments (given 1, expected 0) 
    from (irb):1:in `a' 
    from (irb):4 
    from /home/vagrant/.rbenv/versions/2.3.1/bin/irb:11:in `<main>' 

方法a沒有參數。通過濺出一個空散列來調用它,但散列被存儲在一個變量失敗。這看起來像一個合法的錯誤。任何人有任何想法?

+0

什麼版本的Ruby是你運行? – philomory

+0

@philomory:2.3.1(它正好在輸出:p) – Amadan

+0

令人驚訝的是,這在2.1.3,2.2.0中正常工作,但在2.2.2中失敗。在prev版本中,即使對於'a(** {})'也會拋出異常。猜猜這是一個錯誤,因爲我沒有看到任何文檔證明。 – prasann

回答

0

有一點半知情猜測:在其中使用doublesplat的方法調用會將doublesplat的參數與任何現有的關鍵字參數合併,並將其作爲哈希值放入最後一個參數中。隨着文字空哈希,編譯器可以看到,有沒有關鍵字,並且可以跳過創建一個哈希:

puts RubyVM::InstructionSequence.compile("b = {}; a(**{})").disasm 
== disasm: #<ISeq:<compiled>@<compiled>>================================ 
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: [email protected], kwrest: -1]) 
[ 2] b   
0000 trace   1            ( 1) 
0002 newhash   0 
0004 setlocal_OP__WC__0 2 
0006 putself   
0007 opt_send_without_block <callinfo!mid:a, argc:0, FCALL|ARGS_SIMPLE>, <callcache> 
0010 leave    

你會得到相同的結果,只要關鍵字的總數是可以預見爲零。因此,這兩個編譯相同:

a() 
a(**{}) 

隨着可變的散列(即恰好是空的),這個假設不能進行,並且合併總是被調用,生成一個散列參數:

puts RubyVM::InstructionSequence.compile("b = {}; a(**b)").disasm 
== disasm: #<ISeq:<compiled>@<compiled>>================================ 
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: [email protected], kwrest: -1]) 
[ 2] b   
0000 trace   1            ( 1) 
0002 newhash   0 
0004 setlocal_OP__WC__0 2 
0006 putself   
0007 putspecialobject 1 
0009 getlocal_OP__WC__0 2 
0011 opt_send_without_block <callinfo!mid:core#hash_merge_kwd, argc:1, ARGS_SIMPLE>, <callcache> 
0014 opt_send_without_block <callinfo!mid:dup, argc:0, ARGS_SIMPLE>, <callcache> 
0017 opt_send_without_block <callinfo!mid:a, argc:1, FCALL|ARGS_SIMPLE>, <callcache> 
0020 leave  

所以我想2.2.2添加了一些優化代碼,看到**{}的無用,並跳過生成額外的代碼。

如果你定義你的方法總是收集關鍵字的休息,也不會斷裂,因爲即使發件人沒有通過它的哈希將創建:

def a(**x); p x; end 
a()   # works, x is {} - hash supplied by VM at receiver 
a(**b)  # works, x is {} - hash supplied by sender as b.merge({}) 
a(**{})  # works, x is {} - hash supplied by VM at receiver, **{} ignored 
a(b, **{}) # works, x is {} - hash supplied by sender, **{} ignored 
a({}, **{}) # works, x is {} - hash supplied by sender, **{} ignored 
a(b, **b) # works, x is {} - hash supplied by sender as b.merge(b) 
a({}, **b) # works, x is {} - hash supplied by sender as b.merge({})