Hello
I was trying to make an iterator that goes through a simple tree
structure something like that:
class Node
…
def each_child(&block)
@children.each { |child| yield(child) { child.each_child(&block) } }
end
…
end
so i could generate a simple html menu:
puts “
”
root_node.each_child { |node|
puts “- <a href=”#{child.link}">#{child.title}
"
unless node.children.empty?
puts “”
end
}
puts “
”
But it turns out that Ruby syntax prohibits passing blocks to yield. A
simple workaround is to use block.call instead of yield everywhere.
Is there any good reason for that limitation?
On 28.06.2010 14:52, Andy B. wrote:
}
puts “”
But it turns out that Ruby syntax prohibits passing blocks to yield. A
simple workaround is to use block.call instead of yield everywhere.
Is there any good reason for that limitation?
There is no point in passing a block to yield because yield implicitly
calls the block passed to the current method.
I think what you really want is this:
def each_child(&b)
b[self] # or b.call(self) or simply yield self
@children.each {|ch| ch.each_child(&b)}
self
end
Kind regards
robert
There is no point in passing a block to yield
Maybe there always is a way to avoid using procs and lambdas, but i
don’t see other easy way in my case (see below).
Proc#call accepts blocks, why does yield not? It’s almost the same
thing.
yield implicitly calls the block
passed to the current method.
Thanks for the tip. Forgot that yield belongs to method context only.
def each_child(&b)
b[self] # or b.call(self) or simply yield self
@children.each {|ch| ch.each_child(&b)}
self
end
This code will just pass all the elements to a block, there’s no way to
wrap lower levels into
tag. I need to control what happens before
and after each_child so i have use a number of procs as arguments or do
this trick with a block.
On Mon, Jun 28, 2010 at 7:52 AM, Andy B.
[email protected]wrote:
…
puts “”
Looks like you can get it to work by explicitly taking the block, rather
than trying to yield.
def foo(&block)
block.call 1 do
2
end
end
foo do |param,&block|
puts “The param is #{param}”
puts “The block is #{block.call}”
end
I checked it on 1.8.7 - 1.9.2, it does not work on 1.8.6, though.
2010/6/28 Andy B. [email protected]:
Thanks for the tip. Forgot that yield belongs to method context only.
this trick with a block.
This would not be solved by yield accepting a block however. Your
problem is more complicated. Basically you have a tree structure and
you want to traverse the tree while treating inner nodes and leafs
differently. You would need at least two different blocks to do what
you want. This kind of problem is typically solved with a visitor
pattern [1] or similar solution. Basically you need double dispatch
[2] on the node type and on the algorithm type.
In your case you could do
untested
Child = Struct.new :parent, :info do
def visit(visitor)
visitor.child(self)
end
end
class Parent < Child
def initialize
@children = []
end
def visit(visitor)
visitor.enter_node(self)
@children.each {|ch| ch.visit(visitor)}
visitor.leave_node(self)
end
end
class HtmlPrinter
def enter_node(n)
@indent ? @indent + 1 : 1
puts “#{indent * ’ '}
”
@indent -= 1
end
def child(n)
puts “#{indent * ’ '}
#{n.info}”
end
end
I hope you get the idea.
Kind regards
robert
[1] http://en.wikipedia.org/wiki/Visitor_pattern
[2] Double dispatch - Wikipedia