2016-11-15 44 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({})