8

I have the following Ruby code:

# func1 generates a sequence of items derived from x
# func2 does something with the items generated by func1
def test(x, func1, func2)
    func1.call(x) do | y |
        func2.call(y)
    end
end

func1 = lambda do | x |
    for i in 1 .. 5
        yield x * i
    end
end

func2 = lambda do | y |
    puts y
end


test(2, func1, func2) # Should print '2', '4', '6', '8', and '10'

This does not work, of course.

test.rb:11: no block given (LocalJumpError)
    from test.rb:10:in `each'
    from test.rb:10
    from test.rb:4:in `call'
    from test.rb:4:in `test'
    from test.rb:20
AJM
  • 655
  • 1
  • 9
  • 19

4 Answers4

12

Lambdas don't implicitly accept blocks like regular methods do, so your func1 can't yield. Do this instead:

func1 = lambda do |x, &blk|
  for i in 1 .. 5
    blk.call(x * i)
  end
end

Specifically, I believe this is because yield would send control back to the caller's block, which would not include lambda invocations. So the following code works like you "expect":

def foo
  (lambda { |n| yield(n) }).call(5)
end
foo { |f| puts f }  # prints 5
wuputah
  • 11,285
  • 1
  • 43
  • 60
4

In Ruby 1.9 only:

func1 = lambda do |x, &blk|
  for i in 1..5
    blk.call(x*i)
  end
end
Ken Bloom
  • 57,498
  • 14
  • 111
  • 168
  • 1
    @wuputah: you're right. I see the introduction of this feature in the 1.8.7 NEWS file. However, it's accompanied by a warning "This implementation in current shape is known to be buggy/broken especially with nested block invocation. Take this as an experimental feature." – Ken Bloom Feb 13 '11 at 05:47
  • @Ken Bloom: Nice, I had no idea! Nothing like experimental features in stable Ruby releases. – wuputah Feb 13 '11 at 05:49
  • @wuputah: That extensive number of changes in Ruby 1.8.7 was a big cause of consternation in the Ruby community. I had no idea that there were broken experimental changes in 1.8.7, but given their irresponsibility in making so many changes for 1.8.7 in the first place, I guess it's not too surprising that they'd throw in some broken experimental ones also. (I have to say that after watching this, I agree with the Debian Ruby packagers in saying that Ruby's development practices are *way* out of line with what's considered good practice to actually have your code distributed in a Linux distro.) – Ken Bloom Feb 13 '11 at 05:51
  • Since this feature is experimental in v1.8.7, how much should I try to avoid it? For example, would it perhaps be better to have `func1` return an array containing its generated values, where I'd run `func2` over the array values separately? You should understand that this is really a boiled down version of a more complex algorithm. – AJM Feb 13 '11 at 06:04
  • @AJM: I'd strongly consider returning an array of generated values unless there's serious memory pressure preventing that. Not only does it avoid the experimental language feature, it's probably also much clearer about what's going on. Maybe you could share the more complex algorithm so we can help you make that determination? – Ken Bloom Feb 13 '11 at 06:20
  • The method `test` is really Dijkstra's algorithm, where `x` is the starting node. `func1` returns all nodes adjacent to a given node, while `func2` performs whatever operation I need to do on each visited node. – AJM Feb 13 '11 at 15:39
1
def test(x, func1, func2)
    func1.call(x) do | y |
        func2.call(y)
    end
end

#change func1 to a method
def func1 x
    for i in 1 .. 5
        yield x * i
    end
end

#func2 may be either a method or a lambda
#I changed it for consistency, but you don't have to
def func2 y
    puts y
end


test(2, method(:func1), method(:func2))
Ken Bloom
  • 57,498
  • 14
  • 111
  • 168
0

Based upon Nikita Misharin's answer here:[https://stackoverflow.com/a/45571976/2165560], I like this:

def iterator(x)
  for i in 1 .. 5
    yield x * i
  end
end


iteratorWrapper = -> (m,&block) { iterator(m) {|n| block.call n}  }
iteratorWrapper.call(2) { |y| puts y }

It answers my question here [In Ruby, can you use the lambda or or Proc call method to invoke an iterator?.

By wrapping the iterator it can be arbitrarily passed to other methods and iterate on their blocks.

Mike Stitt
  • 25
  • 4