Returning from captured block

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

https://play.crystal-lang.org/#/r/gdkc

Edit : I tried another solution, still no luck Carcin

Edit 2 : I got something, but find_first returns the last found, instead of the first one Carcin

Ok I got it working, but I find my code very weird - How can it be improved ?

https://play.crystal-lang.org/#/r/gdl4

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…

1 Like

Wheeez, this code is far more better than I could have writen myself (at least for now)

I’m might add that I’m using GPT3.5 (free) which could not help me, or may be I wrongly prompted

Is it frightening ?

Thanks @kojix2

Could you explain the difference between the two ? Are captured those using yield and inline those using call() ?

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 :slight_smile:

If we could use return inside the captured block we could have written

def find
  walk { |node| return node if yield }
end

your last example (Edit2) almost works. The only mistake is that next returns nil, you have to do next r:
https://play.crystal-lang.org/#/r/gdry

1 Like

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.

1 Like

I didn’t think about it, but it totally makes sense in this context - next r behaves “like” a return r,
Thanks to pointing out !