Hi all, I’m strugling to grasp how blocks and yield work on recursive calls
The documentation says
return and break can’t be used inside a captured block. next can be used and will exit and give the value of the captured block.
Basically I would like to have a walk function that I can use for many purposes.
In the example below, I want to use dump() to use walk(), same for find_first() to avoid over repeating the same walk pattern again and again
dump() is easy and ok, but find_first() does not work and always returns nil
Obviously there’s something that I don’t understand
Since I often ask many questions here, I wanted to contribute by providing some answers. However, I couldn’t write them myself, so I had GPT-4 write the code for me. Especially the show method is the work of ChatGPT, I had nothing to do with it.
class Node
getter children : Array(Node)
getter name : String
def initialize(@name : String)
@children = [] of Node
end
def add(child : Node)
@children << child
end
def walk(&block : Node ->)
block.call(self)
@children.each { |child| child.walk(&block) }
end
def find(&block : Node -> Bool)
result = nil
walk { |node| result ||= node if block.call(node) }
result
end
def find_all(&block : Node -> Bool)
matches = [] of Node
walk { |node| matches << node if block.call(node) }
matches
end
def inspect
"Node(#{name})"
end
def show(indent = 0, is_last = true, prefix = "")
result = "#{prefix}#{is_last ? "└─ " : "├─ "}#{name}\n"
@children.each_with_index do |child, index|
child_prefix = is_last ? " " : "│ "
result += child.show(indent + 2, index == @children.size - 1, prefix + child_prefix)
end
result
end
end
root = Node.new("root")
child1 = Node.new("c1")
child2 = Node.new("c2")
child3 = Node.new("c3")
child1.add(child2)
child1.add(child3)
root.add(child1)
p! child1
pp child2
puts root.show
found = root.find { |n| n.name == "c2" }
puts "Found: #{found.inspect}"
I have been using Crystal for 2 years and have not noticed a big difference between captured blocks and inline blocks…
Actually, the behavior of walk and find is different in the new code compared to the original code.
walk only moves nodes.
find accepts blocks that return boolean values. This is equivalent to the original walk.
This is a typical Ruby/Crystal API. ChatGPT does not change the behavior of the original method unless you explicitly tell it to. Human experience is required here
opposite:
inline blocks use yield. Basically, compiler copy-paste content of block instead of yield keyword. This is more effective than capturing so should be preferred when possible.
Captured blocks means that compiler saves block to a closured function and calls it when needed.