如何将方法传递给 Hash#map

How to pass a method to a Hash#map

代码:

a = [1, 2, 3]
h = {a: 1}
def f args
  p args
end
h.map(&method(:f))
a.map(&method(:f))
h.map do |k,v|
  p [k,v]
end

输出:

[:a, 1]
1
2
3
[:a, 1]

为什么我不能为散列定义 f,如下所示?

def f k, v
  p [k, v]
end

看起来,它一定是某种隐式解构(或非严格参数处理),它适用于 procs,但不适用于 lambdas:

irb(main):007:0> Proc.new { |k,v| p [k,v] }.call([1,2])
[1, 2]
=> [1, 2]
irb(main):009:0> lambda { |k,v| p [k,v] }.call([1,2])
ArgumentError: wrong number of arguments (1 for 2)
        from (irb):9:in `block in irb_binding'
        from (irb):9:in `call'
        from (irb):9
        from /home/yuri/.rubies/ruby-2.1.5/bin/irb:11:in `<main>'

但可以让它发挥作用:

irb(main):010:0> lambda { |(k,v)| p [k,v] }.call([1,2])
[1, 2]
=> [1, 2]

因此:

def f ((k, v))
  p [k, v]
end

所以Hash#map总是传递一个参数。

UPD

This implicit destructuring also happens in block arguments.

names = ["Arthur", "Ford", "Trillian"]
ids = [42, 43, 44]
id_names = ids.zip(names)   #=> [[42, "Arthur"], [43, "Ford"], [44, "Trillian"]]
id_names.each do |id, name|
  puts "user #{id} is #{name}"
end

http://globaldev.co.uk/2013/09/ruby-tips-part-2/

UPD 别误会我的意思。我不是建议编写这样的代码 (def f ((k, v)))。在问题中我要求的是解释,而不是解决方案。

你是对的,原因源于 proclambda 之间的两个主要区别之一。我会尝试用与您略有不同的方式来解释它。

考虑:

a = [:a, 1]
h = {a: 1}
def f(k,v)
  p [k, v]
end

a.each(&method(:f))
  #-> in `f': wrong number of arguments (1 for 2) (ArgumentError)
h.each(&method(:f))
  #-> in `f': wrong number of arguments (1 for 2) (ArgumentError)

我使用 #-> 显示打印的内容,使用 #=> 显示 returned 的内容。您使用了 map,但 each 在这里更合适,并且说明了相同的观点。

在这两种情况下,接收器的元素都被传递到块1:

&method(:f)

这(或多或少,我将解释)相当于:

{ |k,v| p [k,v] }

块抱怨(对于数组和散列)它期望两个参数但只收到一个,这是不可接受的。 "Hmmm",reader在思考,"why doesn't it disambiguate in the normal way?"

让我们尝试直接使用该块:

a.map { |k,v| p [k,v] }
  #-> [:a, nil]
  #   [1, nil]
h.map { |k,v| p [k,v] }
  #-> [:a, 1]

这按预期工作,但 return 不是我们想要的数组。 a:a)的第一个元素传入块中,并赋值块变量:

k,v = :a
  #=> :a 
k #=> :a 
v #=> nil

p [k,v]
  #-> :a
  #-> nil

接下来,1 被传递到块并打印 [1,nil]

让我们再尝试一件事,使用通过 Proc::new:

创建的过程
fp = Proc.new { |k,v| p [k, v] }
  #=> #<Proc:0x007ffd6a0a8b00@(irb):34>
fp.lambda?
  #=> false

a.each { |e| fp.call(e) }
  #-> [:a, nil]
  #-> [:a, 1] 
h.each { |e| fp[e] }
  #-> [:a, 1]

(这里我使用了 Proc#call 的三个别名之一。)我们看到调用 proc 与使用块具有相同的结果。 proc 需要两个参数,但只收到一个,但与 lambda 不同,它不会抱怨 2.

这告诉我们需要对 af 做一些小改动:

a = [[:a, 1]]
h = {a: 1}
def f(*(k,v))
  p [k, v]
end

a.each(&method(:f))
  #-> [:a, 1] 
h.each(&method(:f))
  #-> [:a, 1]

顺便说一句,我想你可能用变量名骗了自己 args:

def f args
  p args
end

因为该方法只有一个参数,无论您如何称呼它。 :-)

1 该块是通过 & 在方法 f 上调用 Method#to_proc 然后将 proc(实际上是 lambda)转换为块来创建的。

2 来自 Proc 的文档:"For procs created using lambda or ->() an error is generated if the wrong number of parameters are passed to a Proc with multiple parameters. For procs created using Proc.new or Kernel.proc, extra parameters are silently discarded."