Trouble yielding inside a block/lambda
Asked Answered
R

4

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
Radiothorium answered 13/2, 2011 at 5:33 Comment(1)
possible duplicate of Ruby: yield block from a block?Brownedoff
D
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
Description answered 13/2, 2011 at 5:37 Comment(0)
D
4

In Ruby 1.9 only:

func1 = lambda do |x, &blk|
  for i in 1..5
    blk.call(x*i)
  end
end
Doze answered 13/2, 2011 at 5:39 Comment(6)
@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."Doze
@Ken Bloom: Nice, I had no idea! Nothing like experimental features in stable Ruby releases.Description
@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.)Doze
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.Radiothorium
@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?Doze
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.Radiothorium
D
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))
Doze answered 13/2, 2011 at 13:22 Comment(0)
P
0

Based upon Nikita Misharin's answer here:[https://mcmap.net/q/1324266/-in-ruby-can-you-use-the-lambda-or-or-proc-call-method-to-invoke-an-iterator-duplicate], 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.

Pilgrimage answered 8/8, 2017 at 17:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.