开发者

how to pass a Ruby iterator as a parameter?

开发者 https://www.devze.com 2023-01-21 04:53 出处:网络
I\'d like to write a method that yields values in one place and pass it as a parameter to another method that will invoke it with a block.I\'m convinced it can be done but somehow I\'m not able to fin

I'd like to write a method that yields values in one place and pass it as a parameter to another method that will invoke it with a block. I'm convinced it can be done but somehow I'm not able to find the right syntax.

Here's some sample (non-working) code to illustrate what I'm trying to achieve:

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, in my real code, yielder and user are in different classes.


Update

Thanks for your answers. As Andrew Grimm mentioned, I want the iterator method to take parameters. My original example left this detail out. This snippet provides an iterator that counts up to a given number. To make it work, I made the inner block explicit. It does what I want, but it's a bit ugly. If anyone can improve on this I'd be very interested in seeing how.

def make_iter(upto)
  def i开发者_JAVA技巧ter(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))


This doesn't use a lambda or unbound method, but it is the simplest way to go...

def f
  yield 1
  yield 2
end

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

g :f


When you write &yielder, you're calling yielder and then trying to apply the & (convert-to-Proc) operator on the result. Of course, calling yielder without a block is a no-go. What you want is to get a reference to the method itself. Just change that line to user(method :yielder) and it will work.


I think this might be along the lines of what you want to do:

def yielder
  yield 1
  yield 2
  yield 3
end

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

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

Some related info here: http://blog.sidu.in/2007/11/ruby-blocks-gotchas.html


As it has been pointed out the baseline problem is that when you try to pass a function as a parameter Ruby executes it – as a side effect of parenthesis being optional.

I liked the simplicity of the symbol method that was mentioned before, but I would be afraid of my future self forgetting that one needs to pass the iterator as a symbol to make that work. Being readability a desired feature, you may then wrap your iterator into an object, which you can pass around without fear of having code unexpectedly executed.

Anonymous object as iterator

That is: using an anonymous object with just one fuction as iterator. Pretty immediate to read and understand. But due to the restrictions in the way Ruby handles scope the iterator cannot easily receive parameters: any parameters received in the function iterator are not automatically available within 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 object as iterator

Using a Proc object as iterator lets you easily use any variables passed to the iterator constructor. The dark side: this starts looking weird. Reading the Proc.new block is not immediate for the untrained eye. Also: not being able to use yield makes it a bit uglier IMHO.

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 as iterator

Ideal if you want to obfuscate your code so hard that no one else besides you can read it.

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

Class as iterator

And finally the good ol' OOP approach. A bit verbose to initialize for my taste, but with little or none surprise effect.

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

精彩评论

暂无评论...
验证码 换一张
取 消