简体   繁体   English

为不带参数的方法splatting哈希时的不同行为

[英]different behavior when splatting a hash for a method that takes not arguments

I'm trying to write a helper_method type functionality. 我正在尝试编写一个helper_method类型的功能。 In doing so I encountered this weird behavior: 这样做我遇到了这种奇怪的行为:

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>'

The method a takes no arguments. 方法a不带参数。 Calling it by splatting an empty hash works, but it that hash was stored in a variable it fails. 通过splatting一个空哈希来调用它可以工作,但是这个哈希存储在一个它失败的变量中。 This looks like a legit bug. 这看起来像一个合法的错误。 Anyone has any ideas? 有人有什么想法吗?

A bit of semi-informed guesswork: a method call with doublesplat in it will merge the argument of the doublesplat with any existing keyword arguments, and put it into the last argument as a hash. 一些半知情的猜测:在其中使用doublesplat的方法调用将双面板的参数与任何现有的关键字参数合并,并将其作为散列放入最后一个参数中。 With a literal empty hash, the compiler can see that there are no keywords, and can skip creating a hash: 使用文字空哈希,编译器可以看到没有关键字,并且可以跳过创建哈希:

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: -1@-1, 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            

You will get the same outcome as long as the total number of keywords is predictably zero. 只要关键字总数可预测为零,您就会得到相同的结果。 Thus, these two compile identically: 因此,这两个编译相同:

a()
a(**{})

With a variable hash (that happens to be empty), this assumption cannot be made, and the merge is always called, producing a hash argument: 使用变量哈希(恰好为空),无法进行此假设,并始终调用合并,从而生成哈希参数:

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: -1@-1, 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    

So I guess 2.2.2 added some optimisation code that sees the futility of **{} , and skips generating extra code. 所以我猜2.2.2增加了一些看到**{}无用的优化代码,并跳过生成额外的代码。

If you define your method to always collect the rest of the keywords, it will not break, as a hash will be created even if the sender did not pass it: 如果您定义方法以始终收集其余关键字,则不会中断,因为即使发件人未传递哈希,也会创建哈希:

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({})

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM