Ruby - 为函数提供多个 lambda 或 proc 参数?

Ruby - providing multiple lambda or proc arguments to a function?

我通过 forwardable 模块在 Hash 上实现了基本扩展,在以下源代码中(此处为 public 域)

module OptionsConstants
  THIS = lambda { |a| a.itself }
end


require('forwardable')

class AssocHash

  extend(Forwardable)
  include(Enumerable)
  def_delegators(:@table,
                 :[], :delete, :each, :keys, :values,
                 :length, :empty?,
                 :include?, :has_key?, :key?, :member?
                )

  def self.table_default(whence)
    return lambda { |hash, key| raise("Found no key #{key} in #{whence.class} #{whence.object_id}") }
  end

  def initialize(&keytest)
    @table = Hash.new(&self.class.table_default(self))
    @keytest = ( keytest || OptionsConstants::THIS )
  end
  

  def add(obj, overwrite = false)
    usekey=key(obj)
    if @table.member?(usekey) 
      if (obj == @table[usekey])
        return obj
      elsif ! overwrite
        raise("An object is already registered for key #{usekey} in #{self.class} #{self}")
      end
    end
    @table[usekey]=obj
    return obj
  end

  def get(key)
    return @table[key]
  end

  def key(obj)
    @keytest.call(obj)
  end

end

我仍在研究 Ruby 语言的语法和语义。我想我已经阅读了一些关于如何在 documentation for the Proc class in Ruby 2.6 中的方法参数列表中处理 & 的内容。但是,我不确定我是否完全清楚 &procarg 语法的运作方式。也许它不像 C 中的指针?

为了实现 AsssocHash class,我希望能够将两个 lambda 对象传递给 initialize 方法,理想情况下第二个是可选的,这样然后将使用第二个 lambda 对象(或一般过程)为封装的哈希 @table 提供 'default' 过程。它需要作为 proc 提供给 Hash 初始值设定项。随后,除了直接在 AssocHash.

中使用的 @keytest proc 之外,可以从任何调用 API 提供 default proc

在为此工作时,在本地使用 API 时,似乎使用 &procarg 语法时可能会有一些限制?

目前,我可能只能猜测将任何 object/lambda/proc 传递给另一个方法时如何使用 &。虽然在当前实现中为 Hash 初始值设定项提供默认过程的表达式中添加 & 可能“刚刚起作用”,但坦率地说,我不确定为什么需要它在那里,或者它如何影响 Hash 初始化程序接收的内容。在这段代码中,这似乎与如何向顶级初始化器提供任意两个 proc 表达式的问题无关?

我不确定将其称为 &procarg 是否是最准确的术语。尽管我对 Ruby 语法这方面的理解有限,但 & 语法似乎是为 AssocHash 的初始化程序提供 @keytest 值的参数所必需的, 这样 @keytest 就可以稍后在表达式 @keytest.call

中使用

有没有办法为初始化方法提供第二个过程,这样就可以用作 Hash 初始化方法的过程默认值?

我将在此处尝试对语法进行更多迭代。恐怕似乎没有很多关于 Ruby 方法签名中的 & 限定符的文档。不过,我确信它在源代码中有完整的记录。

更新

也许 & 可能不需要将 lambda 或 proc 传递给方法?

我相信下面的代码可能有助于解决这个问题

module TestConstants
  NOKEY = lambda { |h,k| return "No key: #{k}" }
end

class Test
  def mkhash(fn = TestConstants::NOKEY) 
    Hash.new(&fn)
  end
end

随后,在 irb 下:

irb(main)> t = Test.new
=> #<Test:0x0000557c6fe2e460>

irb(main)> h = t.mkhash
=> {}

irb(main)> h[:a]
=> "No key: a"

irb(main)> h = t.mkhash(lambda { |h,k| return "Not found: #{k}" })
=> {}

irb(main)> h[:b]
=> "Not found: b"

如果可以将 lambda 或 proc 传递给一个方法而不用 & 表示它的参数,那么早期的 AssocHash 代码可能会更新为每个 lambda 值将如何传递给方法。

也许这与每个 lambda 将如何存储在传递给 Hash 构造函数的任何实例变量 and/or 中无关。

至少,它可能有助于解决如何修改该单曲的问题 class。这个 & 限定符肯定是一个有趣的语法,imo.

Ruby 中的方法可能不会超过一个块。但是,您可以将任意数量的块绑定为 proc(或 lambda)并将它们作为常规参数传递。你没有得到很好的块糖语法 (do...end),但它工作得很好。

在 Ruby 中,块(通过 {|...| }do ... end 语法传递给方法的匿名函数)是“一切皆对象”规则的极少数例外之一. Ruby 考虑到将回调函数或匿名函数传递给方法的情况并不少见。您使用块语法传递它,然后可以通过将 &block 参数添加为 arg 列表中的最后一个参数来声明您希望作为 Proc 访问块 ,这获取您传递的匿名函数并将其绑定到存储在变量 block 中的 Proc 实例。这使您可以调用它(通过调用 block.call)或将其作为普通对象实例(如 block)或作为块(如 &block)传递。您还可以使用 yield 隐式调用它而不将其绑定到 Proc.

def debug(obj = nil, &block)
  p [:object, obj]
  p [:block, block]
end

def pass_block_as_proc(&foo)
  puts "Passing as foo"
  debug(foo)
  puts "Passing as &foo"
  debug(&foo)
end

pass_block_as_proc {}

输出:

Passing as foo
[:object, #<Proc:0x000055ee3a724a38>]
[:block, nil]
Passing as &foo
[:object, nil]
[:block, #<Proc:0x000055ee3a724a38>]

所以,如果我们想给一个方法传递多个回调,就不能依赖匿名块糖了。相反,我们必须将它们作为绑定的 Proc 实例传递:

def multiple_procs(value, proc1, proc2)
  proc2.call(proc1.call(value))
end

puts multiple_procs "foo",
      ->(s) { s.upcase },
      ->(s) { s + s }

# => FOOFOO

This link 是一些额外的有用读物,有助于理解块和过程之间的区别。