【问题标题】:how to pass a Ruby iterator as a parameter?如何将 Ruby 迭代器作为参数传递?
【发布时间】:2011-04-28 07:33:45
【问题描述】:

我想编写一个在一个地方产生值的方法,并将其作为参数传递给另一个方法,该方法将使用块调用它。我确信它可以完成,但不知何故我无法找到正确的语法。

这里有一些示例(非工作)代码来说明我想要实现的目标:

def yielder
  yield 1
  yield 2
  yield 3
end

def user(block)
  block.call { |x| puts x }
end

# later...
user(&yielder)

$ ruby x.rb
x.rb:2:in `yielder': no block given (yield) (LocalJumpError)
from x.rb:12:in `<main>'

FWIW,在我的真实代码中,yielder 和 user 属于不同的类。


更新

感谢您的回答。正如 Andrew Grimm 提到的,我希望迭代器方法采用参数。我原来的例子忽略了这个细节。这个 sn-p 提供了一个迭代器,它可以计数到给定的数字。为了使它工作,我明确了内部块。它做我想要的,但它有点难看。如果有人可以对此进行改进,我将非常有兴趣了解如何改进。

def make_iter(upto)
  def iter(upto, block)
    (1 .. upto).each do |v|
      block.call(v)
    end
  end
  lambda { |block| iter(upto, block) }
end

def user(obj)
  obj.call Proc.new { |x| puts x }
end

# later...
user(make_iter(3))

【问题讨论】:

  • 嗯,工作样本会更好。
  • @Tass:我认为显示的代码 epicsmile 足以满足我们的目的。
  • 在已经给出的答案的帮助下进行了一些实验后,我想出了一个工作示例并将其添加为下面的答案。

标签: ruby block yield


【解决方案1】:

这不使用 lambda 或未绑定的方法,但它是最简单的方法...

def f
  yield 1
  yield 2
end

def g x
  send x do |n|
    p n
  end
end

g :f

【讨论】:

  • 我喜欢极简主义的方法。
【解决方案2】:

当您编写&amp;yielder 时,您正在调用yielder,然后尝试对结果应用&amp;(convert-to-Proc)运算符。当然,在没有阻塞的情况下调用yielder 是不行的。您想要的是获得对方法本身的引用。只需将该行更改为 user(method :yielder) 即可。

【讨论】:

  • @Andrew Grimm:基于该规范,仅此而已,很难说最好的方法是什么。显而易见的方法是让user 在调用时传递适当的参数,就像任何其他方法调用一样。如果user 接受一个没有参数的方法,而我们想要传递一个接受参数的方法,那么我们最好知道需要传递哪些参数。在这种情况下,我们可以创建一个像user(lambda {|&amp;block| yielder(some_argument, &amp;block)} 这样的包装函数。
  • @Andrew Grimm:很好的收获。我确实低估了我的要求。我确实想要一个灵活的yielder。我不希望 user() 必须知道 yielder() 的任何参数——这个想法是它是一个不透明的迭代器。我想我可以构建一个带有闭包的过程来捕获正确的值,然后传递它。
【解决方案3】:

我认为这可能符合您想要做的事情:

def yielder
  yield 1
  yield 2
  yield 3
end

def user(meth)
  meth.call { |x| puts x }
end

# later...
user( Object.method(:yielder) )

这里有一些相关信息:http://blog.sidu.in/2007/11/ruby-blocks-gotchas.html

【讨论】:

    【解决方案4】:

    正如已经指出的那样,基线问题是,当您尝试将函数作为参数传递时,Ruby 会执行它——作为可选括号的副作用。

    我喜欢之前提到的符号方法的简单性,但我害怕我未来的自己会忘记需要将迭代器作为符号传递才能使其工作。作为所需的可读性特性,您可以将迭代器包装到一个对象中,您可以传递该对象而不必担心代码会意外执行。

    匿名对象作为迭代器

    即:使用只有一个函数的匿名对象作为迭代器。非常容易阅读和理解。但是由于 Ruby 处理作用域的方式的限制,迭代器不能轻易地接收参数:在函数 iterator 中接收到的任何参数都不会在 each 中自动可用。

    def iterator
        def each
            yield("Value 1")
            yield("Value 2")
            yield("Value 3")
        end
    end
    
    def iterate(my_iterator)
        my_iterator.each do |value|
            puts value
        end
    end
    
    iterate iterator
    

    Proc 对象作为迭代器

    使用Proc 对象作为迭代器可以让您轻松使用传递给迭代器构造函数的任何变量。阴暗面:这开始看起来很奇怪。对于未经训练的眼睛来说,阅读Proc.new 块并不是立即的。另外:无法使用yield 恕我直言,它有点难看。

    def iterator(prefix:)
        Proc.new { |&block|
            block.call("#{prefix} Value 1")
            block.call("#{prefix} Value 2")
            block.call("#{prefix} Value 3")
        }
    end
    
    def iterate(my_iterator)
        my_iterator.call do |value|
            puts value
        end
    end
    
    iterate iterator(prefix: 'The')
    

    Lambda 作为迭代器

    如果您想对代码进行如此复杂的混淆,以至于除了您之外没有其他人可以阅读它,这是理想的选择。

    def iterator(prefix:)
        -> (&block) {
            block.call("#{prefix} Value 1")
            block.call("#{prefix} Value 2")
            block.call("#{prefix} Value 3")
        }
    end
    
    def iterate(my_iterator)
        my_iterator.call do |value|
            puts value
        end
    end
    
    iterate iterator(prefix: 'The')
    

    作为迭代器的类

    最后是良好的 OOP 方法。根据我的喜好,初始化有点冗长,但几乎没有或没有惊喜效果。

    class Iterator
        def initialize(prefix:)
            @prefix = prefix
        end
    
        def each
            yield("#{@prefix} Value 1")
            yield("#{@prefix} Value 2")
            yield("#{@prefix} Value 3")
        end
    end
    
    def iterate(my_iterator)
        my_iterator.each do |value|
            puts value
        end
    end
    
    iterate Iterator.new(prefix: 'The')
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-12-13
      • 1970-01-01
      • 1970-01-01
      • 2023-03-19
      • 2020-05-31
      • 2021-11-28
      • 1970-01-01
      • 2015-04-07
      相关资源
      最近更新 更多