From: "matheusrich (Matheus Richard) via ruby-core" Date: 2024-07-10T18:56:43+00:00 Subject: [ruby-core:118535] [Ruby master Feature#20625] Object#chain_of Issue #20625 has been reported by matheusrich (Matheus Richard). ---------------------------------------- Feature #20625: Object#chain_of https://bugs.ruby-lang.org/issues/20625 * Author: matheusrich (Matheus Richard) * Status: Open ---------------------------------------- ## Motivation It's often common to traverse a tree/list-like structure in order to get a chain of elements. This proposal is to add a method to `Object` that allows collecting a chain of elements by applying a block to each element. It doesn't require the root element to be an instance of a specific class or respond to a specific protocol. I think this method could be useful in many cases, since hierarchies like this are common in codebases (e.g. file systems, organizations structures, commit histories, breadcrumbs in web apps, configuration hierarchies, etc.). Here are some examples extracted from real codebases (simplified for the sake of the example/privacy): ```rb # Given a file system structure, get the breadcrumbs of a file or directory def breadcrumbs(root) crumbs = [] current = root while current crumbs << current current = current.parent_dir end crumbs end # Given an employee, get the hierarchy of managers def hierarchy(employee) hierarchy = [] current = employee while current hierarchy << current current = current.manager end hierarchy end ``` ## Implementation The implementation in Ruby could look like this: ```rb class Object def chain_of(&block) chain = [] current = self while current chain << current current = block.call(current) end chain end end ``` Here's an example use: ```rb class ListNode attr_accessor :value, :parent def initialize(value, parent = nil) @value = value @parent = parent end def ancestors chain_of(&:parent).shift end end root = ListNode.new("root") child1 = ListNode.new("child1", root) child2 = ListNode.new("child2", child1) puts child2.ancestors.map(&:value) # => ["child1", "root"] ``` The examples from the motivation section could be rewritten as: ```rb breadcrumbs = root.chain_of(&:parent_directory) hierarchy = employee.chain_of(&:manager) ``` ## Considerations - While I'm including the object by default in the chain, it could be more intuitive to exclude it. In any case, it's easy to remove or add it with `shift`/`unshift`. - On a different note, the method could be named differently (I do like `chain_of`, though). Some alternatives I've considered are `map_chain`, `traverse`, and `trace_path`. - The method assumes that the traversal will finish at some point. If the user has a cyclic structure, it will loop indefinitely. We could stop looping if we find the same element twice. I don't think it's worth the extra complexity. - I'm not sure `Object` is the best place for this method. While it's very general, I think it gives power to the user to decide how to traverse a chain like this without having to rely on a specific class. Maybe a mixin (`Traversable`/`Chainable`) would be more appropriate? Could this fit in `Enumerable`, somehow? Of course, I'm open to suggestions and feedback. Thanks for reading! -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/