TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Programming languages should have a tree traversal primitive

288 pointsby azhenley14 days ago

66 comments

aeonik14 days ago
I agree with the author, we need better primitives, if you need functionality now:<p>Major tools that exist today for partial structure traversal and focused manipulation:<p>- Optics (Lenses, Prisms, Traversals)<p><pre><code> Elegant, composable ways to zoom into, modify, and rebuild structures. Examples: Haskell&#x27;s `lens`, Scala&#x27;s Monocle, Clojure&#x27;s Specter. Think of these as programmable accessors and updaters. </code></pre> - Zippers<p><pre><code> Data structures with a &quot;focused cursor&quot; that allow local edits without manually traversing the whole structure. Examples: Huet’s original Zipper (1997), Haskell’s `Data.Tree.Zipper`, Clojure’s built-in zippers. </code></pre> - Query Languages (for semantic traversal and deep search)<p><pre><code> When paths aren&#x27;t enough and you need semantic conditionals: - SPARQL (semantic web graph querying) - Datalog (logic programming and query over facts) - Cypher (graph traversal in Neo4j) - Prolog (pure logic exploration) These approaches let you declaratively state what you want instead of manually specifying traversal steps.</code></pre>
评论 #43833317 未加载
评论 #43832975 未加载
评论 #43837689 未加载
评论 #43832550 未加载
评论 #43842151 未加载
评论 #43834789 未加载
评论 #43839367 未加载
评论 #43835629 未加载
评论 #43837079 未加载
评论 #43833928 未加载
Xmd5a14 days ago
Tree traversal primitives (clojure.walk):<p><pre><code> (defn walk [inner outer form] (cond (list? form) (outer (with-meta (apply list (map inner form)) (meta form))) (instance? clojure.lang.IMapEntry form) (outer (clojure.lang.MapEntry&#x2F;create (inner (key form)) (inner (val form)))) (seq? form) (outer (with-meta (doall (map inner form)) (meta form))) (instance? clojure.lang.IRecord form) (outer (reduce (fn [r x] (conj r (inner x))) form form)) (coll? form) (outer (into (empty form) (map inner form))) :else (outer form))) (defn postwalk [f form] (walk (partial postwalk f) f form)) (defn prewalk [f form] (walk (partial prewalk f) identity (f form))) </code></pre> Another reason why this perlisism holds:<p><pre><code> 9. It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures. </code></pre> &quot;Let&#x27;s move on.&quot;
评论 #43834757 未加载
评论 #43833102 未加载
评论 #43840662 未加载
评论 #43840857 未加载
pgt14 days ago
Nathan Marz&#x27; talk on Specter (Clojure Library that decouples navigation from transformation) is must-watch if you deal with data: <a href="https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=VTCy_DkAJGk" rel="nofollow">https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=VTCy_DkAJGk</a><p>I use it in every project for data navigation and transformation, and it&#x27;s more performant than standard Clojure data manipulation, while retaining types (instead of coercing back from seqs).<p>E.g. if you have a map and you want to increment every value in the map: (require &#x27;[com.rpl.specter :as S])<p>``` (-&gt;&gt; {:a 5, :b 6, :c 7} (S&#x2F;transform [S&#x2F;MAP-VALS] inc)) =&gt; {:a 6, :b 7, :c 8} ```<p>^ try to write that in normal clojure.<p>Now let&#x27;s say you have a map of vectors and want to increment all of those? (-&gt;&gt; {:a 5, :b 6, :c 7} (S&#x2F;transform [S&#x2F;MAP-VALS S&#x2F;ALL] inc)) ;; note the navigator juts got another level of nesting =&gt; {:a [2 3], :b [4 5], :c [6 7]}works for all clj data types, and of course it has navigators for recursive walking .<p>It took me a while to get good at Specter, but it was worth it. I hear Rama uses Specter navigators internally.
评论 #43845115 未加载
jerf14 days ago
That&#x27;s &quot;just&quot; a particular kind of fancy iterator that you should be able to implement in any language with iterator support. Here&#x27;s one in Python:<p><pre><code> # Expects: # Tuple of iterateOn where iterateOn can be None def fancyIt(init, *options): if init != None: yield(init) for f in options: newVal = f(init) yield from fancyIt(newVal, *options) class Tree: def __init__(self, val, left = None, right = None): self.val = val self.left = left self.right = right def left(self): return self.left def right(self): return self.right myTree = Tree( 1, Tree(2, Tree(3), Tree(4, None, Tree(5))), Tree(6, None, Tree(7))) for node in fancyIt(myTree, Tree.left, Tree.right): print(node.val) </code></pre> which prints the numbers 1 through 7 in order.<p>Breadth-first is slightly trickier, but only slightly trickier one time.
评论 #43834180 未加载
评论 #43833018 未加载
评论 #43833144 未加载
评论 #43835305 未加载
评论 #43838437 未加载
评论 #43834668 未加载
monkeyelite14 days ago
This is solved by iterators in C++. The idea of an iterators is to generalize the concept of a pointer —- something which refers to a location in your data structure.<p>For example the most basic operations of a pointer are to advance and dereference.<p>std::map is actually implemented as a tree. To iterator its members you can do<p><pre><code> for (cost auto &amp;pair : map) </code></pre> The only requirement for your custom data structure to work is to implement begin() and end() which return iterators - “pointer like” objects.
评论 #43842113 未加载
yxhuvud14 days ago
No, that seems like language bloat. Better to make your language have internal iterators, as then data structures can add their own logic that matches their need for iteration. Then special cases don&#x27;t need additional syntax.
评论 #43834106 未加载
ivanjermakov14 days ago
I think functional languages handle this nicely with Foldable or Traversable typeclasses: <a href="https:&#x2F;&#x2F;hackage.haskell.org&#x2F;package&#x2F;base-4.21.0.0&#x2F;docs&#x2F;Data-Foldable.html" rel="nofollow">https:&#x2F;&#x2F;hackage.haskell.org&#x2F;package&#x2F;base-4.21.0.0&#x2F;docs&#x2F;Data-...</a>
评论 #43834628 未加载
评论 #43839101 未加载
评论 #43834652 未加载
pmontra14 days ago
I remember that I was using trees quite often last century, when I was writing programs in C. I seldom use tree or comparably complex data structures nowadays, when I almost only write web apps (mostly backend in Ruby or Python but some frontend in JS.) I&#x27;m bet that both Ruby and Python have plenty of trees written in C inside their interpreters. I do everything with arrays (lists) and hash tables (dicts) in Ruby and Python. Maybe a language construct for trees would be nice for lower level languages and almost out of scope for higher level ones.<p>The same from another angle: there are a lot of trees in the indices of SQL databases (example [1]) but we don&#x27;t zoom in to that level of detail very often when defining our tables.<p>[1] <a href="https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;current&#x2F;btree.html" rel="nofollow">https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;current&#x2F;btree.html</a>
评论 #43835783 未加载
评论 #43844376 未加载
评论 #43833685 未加载
Retr0id14 days ago
&gt; &quot;Why not just use recursive functions&quot;<p>One great reason not to use recursive functions for traversing trees is that you can allocate your own stack data structure rather than relying on the call stack itself. In <i>most</i> languages&#x2F;runtimes, the call stack has a maximum depth which limits the depth of trees you can process, usually on the order of thousands of stack frames.<p>Managing your own stack usually produces weirder looking code (personally I find &quot;naive&quot; recursive approaches more readable) - but having it as a first-class language feature could solve that!
评论 #43834801 未加载
swiftcoder13 days ago
To some extent, this seems like it&#x27;s just a symptom of &quot;writing iterators in C++ is harder than it ought to be&quot;. You don&#x27;t need to have a tree in memory to build an iterator over it - folks write iterators over implicit data all the time.<p>Here&#x27;s the range based for loop counterexample from the blog post, as a python generator:<p><pre><code> import itertools def iterate_tree(): for length in range(1, 8): for combo in itertools.product(&quot;abc&quot;, repeat=length): yield &#x27;&#x27;.join(combo) for s in iterate_tree(): print(s)</code></pre>
评论 #43842919 未加载
评论 #43845844 未加载
soegaard14 days ago
Pick a language that allows users to define their own control structures and test your idea in practise.<p>Candidates: Racket, Scheme, Rust.
评论 #43833096 未加载
评论 #43844386 未加载
lutusp14 days ago
The article&#x27;s thesis relies on the idea that a genuinely primitive traversal action exists, in the way that a for-loop is primitive and widely applicable, or adding two floats is common enough to justify building it into the language (or processor).<p>But tree traversal doesn&#x27;t have this universal property. There are too many methods and purposes for traversing a tree, sufficient that IMHO no single primitive embodiment could materially improve a language. Also, modern compilers efficiently break down high-level traversal code so well that expressing the idea at a high level incurs no serious penalty compared to having a primitive for that purpose, or a series of them.
评论 #43834504 未加载
jpmonettas14 days ago
Clojure has tree-seq <a href="https:&#x2F;&#x2F;clojuredocs.org&#x2F;clojure.core&#x2F;tree-seq" rel="nofollow">https:&#x2F;&#x2F;clojuredocs.org&#x2F;clojure.core&#x2F;tree-seq</a>
cdrini14 days ago
I&#x27;m not sure if it needs to be at the syntax level, but I think having built in standard library support for graphs (general trees) would help make graphs more common in programming. And seeing how powerful they are, I think that would be a good thing!<p>I explored this idea with gstd, a standard library for graphs inspired by the JS Array interface: <a href="https:&#x2F;&#x2F;github.com&#x2F;cdrini&#x2F;gstd&#x2F;">https:&#x2F;&#x2F;github.com&#x2F;cdrini&#x2F;gstd&#x2F;</a>
injidup14 days ago
That&#x27;s what c++20 coroutines paired with c++23 generators are for. It&#x27;s easy to define whatever traversal you want and then expose it as generic code.<p><a href="https:&#x2F;&#x2F;godbolt.org&#x2F;z&#x2F;fnGzszf3j" rel="nofollow">https:&#x2F;&#x2F;godbolt.org&#x2F;z&#x2F;fnGzszf3j</a>
评论 #43842068 未加载
评论 #43839852 未加载
mubou14 days ago
I bet you could do something generic like this in languages that have deferred execution like C#&#x27;s IEnumerable. Something like<p><pre><code> foreach (Node node in EnumerateNodes(root, x =&gt; x != null, x =&gt; [x.Left, x.Right])) </code></pre> where EnumerateNodes uses `yield return` (i.e. is a generator) and calls itself recursively. Though it&#x27;d probably be easier &#x2F; better performance to write an implementation specific to each node type.
taeric14 days ago
Tree traversal is an odd one to want to try and make primitive. There are a lot of hidden questions that you need to consider when traversing a tree. Would you want your syntax to indicate what order traversal? Should it indicate a way to control the extra memory that you allow for the common stack&#x2F;queue used in basic traversal? If you have a threaded tree, should the syntax work without any extra memory usage?
JaumeGreen13 days ago
One of the projects that I have only in my mind space is an Iversonian language with more data structures and the tools to easily navigate an traverse them. Trees, graphs, ... the sky is the limit. And with the power of notation as a tool of thought, and a pen interface, it would be fun to do whiteboard sessions that produced real code that could be executed as is.<p>OTOH I read&#x2F;heard that the beauty of array languages is that they have few data types, but they can be easily worked on. So maybe the answer is not easier tree traversal primitives, but better literature&#x2F;training on how to transform it in a more manageable data type.<p>Sometimes the answer is to adapt our vision to our world, sometimes the answer is to adapt the world to our vision.
branko_d13 days ago
In C#, you can implement an iterator method with the signature like this:<p><pre><code> public static IEnumerable&lt;TNode&gt; DepthFirstTraverse&lt;TNode&gt;( TNode root, Func&lt;TNode, IEnumerable&lt;TNode&gt;&gt; children ) </code></pre> You can then just ‘foreach’ over it.<p>This doesn’t require the whole tree to be in-memory. Optimal implementation would recurse via programmer-controlled stack (instead of language-controlled) to avoid nested iterators.<p>Here is a TypeScript version:<p><pre><code> function* traverseDepthFirst&lt;T&gt;( root: T, children: (parent: T) =&gt; Iterable&lt;T&gt; ) </code></pre> A similar approach should be possible in any language which supports iterators&#x2F;generators.
HelloNurse14 days ago
The simplifying assumption that all tree nodes have the same type and the same function should be called on them seems unrealistic.<p>This kind of imperative iteration seems better served by the traditional visitor design pattern: more verbose (more explicit, not more complex) and more general.
hyperhello14 days ago
This is interesting, but the syntax doesn&#x27;t seem to have the right expressiveness for such a large change.<p><pre><code> for recursive (Node t = tree.root; t != NULL;) { puts(t.value); if (t.value == target) break; if (t.value == dontfollow) continue; if (t.left) continue t.left; if (t.right) continue t.right; } return t; </code></pre> Regular &#x27;break&#x27; is to really break out of the structure like a regular for, as regular &#x27;continue&#x27; is to do the next iteration. But if continue has a value to recurse on, it reenters the for loop like a subroutine.<p>As a bonus, I think this is tail-call-optimization friendly.
评论 #43836401 未加载
gue5t14 days ago
It seems like this is grasping for languages to expose recursors[1] or induction principles. These factor out the structure of an inductive type (e.g. trees) and leave the caller to simply plug in the behavior to perform for each possible situation that might arise during traversal.<p>[1]: <a href="https:&#x2F;&#x2F;lean-lang.org&#x2F;doc&#x2F;reference&#x2F;latest&#x2F;&#x2F;The-Type-System&#x2F;Inductive-Types&#x2F;#recursors" rel="nofollow">https:&#x2F;&#x2F;lean-lang.org&#x2F;doc&#x2F;reference&#x2F;latest&#x2F;&#x2F;The-Type-System&#x2F;...</a>
andyferris14 days ago
My thoughts on this are to add a third keyword alongside `break` and `continue` to loops that allow you to push another (loop) stack frame and descend into a tree-like data structure.<p>For exposition, lets seperate loop initialization from the rest, make `continue` mandatory to repeat the loop with another specified value (i.e. the loop implicitly ends without a continue) and lets say `descend` to recurse into the tree.<p><pre><code> sum = 0; loop (node = tree.root) { sum += node.value; if (node.left) descend node.left; if (node.right) descend node.right; } </code></pre> Compare with a linear loop:<p><pre><code> sum = 0; loop (i = 0) { if (i &gt;= array.length) break; sum += array[i]; continue i + 1; } </code></pre> The `descend` keyword differs from `continue` in that its more of a continuation - control flow will continue from that point once it is done.<p>You could make this more functional and less imperative (and I&#x27;d be surprised if such things don&#x27;t exist as libraries in some functional languages). Yes ultimately we can transform this to recursive functions and compile it that way... but for imperative languages (especially those without closures) something like the above might be useful.<p>Arguably the above loops are _more_ imperative and less declaritive than the standard ones from the C family. You could add a functional twist by breaking with a value (which is the result of the overall `loop` expression).
vinceguidry14 days ago
When I went looking for this in Ruby, I eventually landed on RubyTree[1]. Being able to subclass the TreeNode makes everything so much friendlier. Sure, I could implement them myself, but getting converters to&#x2F;from primitives for free is a lot off my mind. Would be nice to have it built into the language but honestly Ruby has a whole lot of stdlibs and default gems already.<p>1: <a href="https:&#x2F;&#x2F;github.com&#x2F;evolve75&#x2F;RubyTree">https:&#x2F;&#x2F;github.com&#x2F;evolve75&#x2F;RubyTree</a>
hinkley14 days ago
I think Java did the right thing by making tree shaped collections, since they’re iterable and that covers the for loop situation, but it doesn’t expose the tree structure, so you can’t tell if two nodes are siblings or relatives or indeed if they are even in the same tree.<p>But I think grafting those two together is the right answer, not inventing a new loop construct.<p>Trees are easy to write iterators for. DAGs are a bit harder and full graphs are an advanced interview question.
john-h-k14 days ago
Excellent time to mention rust&#x27;s `ControlFlow` enum which allows doing this exact sort of thing <a href="https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;ops&#x2F;enum.ControlFlow.html" rel="nofollow">https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;ops&#x2F;enum.ControlFlow.html</a><p>Not as ergonomic as a direct tree-iterator, but I can&#x27;t see of an elegant way to introduce that in an imperative language while keeping the forking&#x2F;recursion aspect clear
Someone13 days ago
I would start with a <i>tree</i> primitive. That’s difficult enough, with variations on branching factor, whether the tree is self-balancing, whether it is immutable, etc)<p>I guess&#x2F;expect that will have to expose the implementation detail of the branching factor (yes, a binary tree is a special-case of a <a href="https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;B-tree" rel="nofollow">https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;B-tree</a> for B = 2, but algorithms tend to be different)<p>On top of that, build a library of tree traversal routines (breadth first, depth first with a few variants), allowing the user to specify code that decides, on each node visited, whether to navigate further down and whether to stop the traversal.<p>Now, whether the <i>language</i> proper needs a generic way to specify tree traversals? I doubt it, and doubt even more that it is possible too that in a way so that a) most graph traversal algorithms can use the primitive and b) the result is easier to use&#x2F;looks significantly nicer than just calling those routines.
meltyness14 days ago
For perspective, I&#x27;m a novice with algorithm design, I&#x27;ve been grinding leetcode for the past 6 months or so, almost exclusively in Rust. I was bewildered by the same concern since I had initially set out to, not only focus on Rust, but to primarily maximize use of the Iterator construct, since I was not intricately familiar with it. A few months in I discovered that there was an appropriate Iterator construct which accomplishes the same thing.<p><pre><code> &#x2F;&#x2F; Comments for the non-Rust native reader, regarding this Function declaration: &#x2F;&#x2F; successors is a function that accepts an `Option` container for some Value of type T, called `first` &#x2F;&#x2F; and a Closure called `succ`, constrained below: pub fn successors&lt;T, F&gt;(first: Option&lt;T&gt;, succ: F) -&gt; Successors&lt;T, F&gt; ⓘ where &#x2F;&#x2F; `succ` must receive the iterated state, and return the next iterated state F: FnMut(&amp;T) -&gt; Option&lt;T&gt;, &#x2F;&#x2F; Each time the `next()` function is called on the returned Iterator (a Successors-flavored iterator), &#x2F;&#x2F; the state of `first` is yielded, and then &#x2F;&#x2F; `succ` is called to progress &#x2F;&#x2F; until a `None` type is reported by `succ` </code></pre> I&#x27;m not sure where the concept came from, but it&#x27;s not dissimilar to the author&#x27;s implementation, but instead of the ControlFlow enum, it relies simply on the Option enum. I know though, that it was initially built in the Itertools crate as unfold and then upstreamed some time later.<p>Essentially you use `first` to contain a Queue, Stack, or Level for the different traversals, and define traversal or activities from there.<p>It&#x27;s fairly ergonomic in practice, ergonomic enough for Leetcode.<p>Here&#x27;s a BFS: <a href="https:&#x2F;&#x2F;leetcode.com&#x2F;problems&#x2F;course-schedule-iv&#x2F;solutions&#x2F;6297286&#x2F;functional-iterator-approach-with-successors-2ms&#x2F;" rel="nofollow">https:&#x2F;&#x2F;leetcode.com&#x2F;problems&#x2F;course-schedule-iv&#x2F;solutions&#x2F;6...</a><p>[0] <a href="https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;iter&#x2F;fn.successors.html" rel="nofollow">https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;iter&#x2F;fn.successors.html</a><p>[1] <a href="https:&#x2F;&#x2F;docs.rs&#x2F;itertools&#x2F;latest&#x2F;itertools&#x2F;fn.unfold.html" rel="nofollow">https:&#x2F;&#x2F;docs.rs&#x2F;itertools&#x2F;latest&#x2F;itertools&#x2F;fn.unfold.html</a>
bjourne14 days ago
I&#x27;m 100% sure smug Haskellers have something very important to say here. :)
评论 #43839197 未加载
评论 #43833540 未加载
tbrownaw14 days ago
Standard tree traversal loop (yes, missing some obvious conditionals I didn&#x27;t feel like typing out):<p><pre><code> var todo = new List&lt;T&gt;(); todo.append(root); while (var item = todo.pop_front()) { todo.append(item.left); &#x2F;&#x2F; or .prepend for depth-first todo.append(item.right); &#x2F;&#x2F; or .prepend() &#x2F;&#x2F; do stuff... }</code></pre>
pseudocomposer14 days ago
Couldn&#x27;t you just do this with a regular for loop and a few datatypes&#x2F;functions? (This pseudocode is an Elm&#x2F;Haskell&#x2F;Rust&#x2F;TypeScript-inspired abomination, but pretty portable to any language...)<p><pre><code> type Node = { value: Any, left: Node, right: Node } type Direction = Left | Right type TreePosition = { root: Node, currentNode: Node = root, position: Direction[] = [] } # Implementation left as an exercise but should be obvious and run in O(1), I believe. Returns Nothing when we&#x27;re out of nodes. function nextPosition(position: TreePosition): Option&lt;TreePosition&gt; # The tree you want to iterate through const myTree: Node = ... # The loop for(let position: TreePosition? = TreePosition(root: myTree); position != Nothing; position = nextPosition(position) { node = position!.currentNode # Your loop code } </code></pre> I&#x27;d argue this doesn&#x27;t belong as a language-level feature, but maybe an API&#x2F;stdlib-level feature.
评论 #43832972 未加载
sttaft14 days ago
For what it&#x27;s worth, the ParaSail parallel programming language (<a href="https:&#x2F;&#x2F;github.com&#x2F;parasail-lang&#x2F;parasail">https:&#x2F;&#x2F;github.com&#x2F;parasail-lang&#x2F;parasail</a>) supports this:<p><pre><code> func Search(Root : Tree; Desired_Value : Tree_Value) -&gt; optional Tree_Id is var Identifier : optional Tree_Id := null; for T =&gt; Root then Left(T) || Right(T) while T not null concurrent loop if Value(T) == Desired_Value then &#x2F;&#x2F; Found desired node, &#x2F;&#x2F; exit with its identifier exit loop with Identifier =&gt; Key(T); end if; end loop; return Identifier; end func Search; </code></pre> I actually don&#x27;t use this feature much, because usually there is a key of some sort that determines which side of the tree is of interest.
pbohun14 days ago
I think this kind of control mechanism is not a great idea and could lead to easy bugs.<p>I think it&#x27;s a better idea to write a function that applies a function to the elements of a tree. Ideally you&#x27;d make a function for each traversal order. This makes it obvious what is happening.<p>map_tree_pre(my_tree, my_func);<p>map_tree_in(my_tree, my_func);<p>map_tree_post(my_tree, my_func);<p>map_tree_bfs(my_tree, my_func);
adamc14 days ago
Or maybe you need a different language, where implementing an efficient tree traversal operation is easier?
foota14 days ago
One thing I wish for is for standard library trees to present a way to take advantage of a trees structure, even if they don&#x27;t directly expose the tree itself. For instance, C++ map could expose a way to start find or e.g., lower_bound from some node. While I&#x27;m making a wishlist, I also wish lower_bound worked with reverse iterators (see e g., <a href="https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;64351187&#x2F;stdlower-bound-on-a-set-with-its-reverse-order" rel="nofollow">https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;64351187&#x2F;stdlower-bound-...</a>) :)<p>I think allowing for starting this kind of search from a given node would cover most (though not all) of what you&#x27;d want to expose the trees structure for directly.
scotty7913 days ago
Branching iteration seems like a great idea. But I think the syntax shouldn&#x27;t come from tree traversal. It should be something general. You tell the normal for loop how to go to the next sibling (`increment`) and when to end naturally (`condition`) or prematurely (`break`) and how to skip current sibling (in whole or partially) (`continue`). Branching iteration would also need to know how and when to branch, that includes skipping branching for this particular item, end whole iteration naturally and prematurely.<p>So something similar to `break` and `continue` should be the method of branching (descending into the tree).<p>I think Scala might be a great language to prototype implementation of such construct.
mpweiher13 days ago
Adaptive Programming[1] by Karl Lieberherr is the most comprehensive attempt I have seen to formalize and separate iteration over arbitrary structures as a distinct PL construct.<p>It allows you to specify traversal in a structure-shy way. Personally, I think it goes a little too far, but it&#x27;s definitely a good inspiration.<p>[1] <a href="https:&#x2F;&#x2F;www2.ccs.neu.edu&#x2F;research&#x2F;demeter&#x2F;" rel="nofollow">https:&#x2F;&#x2F;www2.ccs.neu.edu&#x2F;research&#x2F;demeter&#x2F;</a><p>Presentation: <a href="https:&#x2F;&#x2F;www2.ccs.neu.edu&#x2F;research&#x2F;demeter&#x2F;talks&#x2F;overview&#x2F;quad2000.pdf" rel="nofollow">https:&#x2F;&#x2F;www2.ccs.neu.edu&#x2F;research&#x2F;demeter&#x2F;talks&#x2F;overview&#x2F;qua...</a>
bee_rider14 days ago
What is this note before the code about?<p>&gt; This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.<p>Also I’m slightly confused by this example.<p><pre><code> for_tree(string x = &quot;&quot;; x.size() &lt;= 8; x : {x+&quot;a&quot;, x+&quot;b&quot;, x+&quot;c&quot;}){ print(x);</code></pre> }<p>So, our “next node” operation is to concatenate to x. Won’t we either have to have a method for modifying x to go “up” a node, or we’ll have to keep a record of what x was upon entering each node? Like in this example we’ll end up with x=“aaaaaaaa” and then go up a node, over to a “b” node, and get x=“aaaaaaaab”, right?
评论 #43832999 未加载
评论 #43832870 未加载
demarq14 days ago
I would think iterators are exactly this. And they exist in almost every language.
kelseyfrog14 days ago
I&#x27;ve written about this before, but suggesting recursion for tree traversal is equivalent to suggesting goto is a replacement for if&#x2F;for&#x2F;while.<p>We don&#x27;t have syntax and semantics for recursion schemes in any programming language - it&#x27;s always deferred to library support at best. As far as I&#x27;m concerned, this is an open problem in programing language design where we finally replace the vestigial recursion hack with a proper structured programming solution.
评论 #43838829 未加载
JonChesterfield14 days ago
This implementation recurses on the call stack and calls that out as a feature. A built into the language tree walk which overflows the stack on large trees is perfect for C++, get a paper over to WG21.<p>Or for a possibly more useful comment, constant space tree traversal is genuinely quite difficult to implement. I don&#x27;t know a general solution to it (other than walk from the start N times, quadratic style), would be interested to hear of one.
评论 #43858445 未加载
评论 #43834325 未加载
评论 #43834171 未加载
MontagFTB14 days ago
Sean Parent developed ‘forest’ for C++ that automatically maintains the invariant that its structure is always a hierarchy. It includes full-order iteration through its default iterator: <a href="https:&#x2F;&#x2F;stlab.cc&#x2F;2020&#x2F;12&#x2F;01&#x2F;forest-introduction.html" rel="nofollow">https:&#x2F;&#x2F;stlab.cc&#x2F;2020&#x2F;12&#x2F;01&#x2F;forest-introduction.html</a>
评论 #43833189 未加载
lucasoshiro14 days ago
This is heavy biased to C++, which is a language that already has too many features that people just want to use. This is a very specific case to be placed as a language feature, and could be just a lib.<p>If this becomes a C++ feature, imagine how many data structures we would need to support?<p>Many other languages, specially the FP languages, allow to do that as a library. Even the languages that are only inspired by FP. Example, Ruby:<p><pre><code> class BinTree include Enumerable def initialize v, l, r @v, @l, @r = v, l, r end def each &amp;block @l.each(&amp;block) unless @l.nil? yield @v @r.each(&amp;block) unless @r.nil? end end </code></pre> Using the Enumerable mixin includes many FP-based methods, such as map, filter and reduce by only defining each, which in this case is DFS.<p>Then we can proceed to define a binary tree:<p><pre><code> tree = BinTree.new( 1, BinTree.new( 2, BinTree.new(4, nil, nil), BinTree.new(5, nil, nil) ), BinTree.new( 3, BinTree.new(6, nil, nil), BinTree.new(7, nil, nil), ) ) </code></pre> Iterate over all the elements:<p><pre><code> tree.each{|v| puts v} </code></pre> Iterate over the even elements:<p><pre><code> tree.filter{|v| v.even?}.each{|v| puts v} </code></pre> Stop iteration when finding a value:<p><pre><code> tree.each do |v| break if v == 1 puts v end </code></pre> And so on. The same can be done in Python, Kotlin and many others.
评论 #43833324 未加载
jonstewart14 days ago
I worked for Guidance Software in the aughts and their main product, EnCase, had its own proprietary scripting language built in, called EnScript. Everything in EnCase inherited from “NodeClass”, a linked list for creating trees.<p>EnScript had a forall(NodeClass n in tree.GetRoot(){} construct that was very easy. It was essentially a depth-first iterator.
globular-toast13 days ago
For isn&#x27;t &quot;linear traversal&quot;, it&#x27;s linear execution. It&#x27;s up to the data structure to define what linear traversal is. For arrays and lists that&#x27;s trivial. For trees and, more generally, graphs there are many ways to do traversal. This is what iterators are for.
systems14 days ago
well, functional languages with recursive types, are very good at representing binary trees<p><a href="https:&#x2F;&#x2F;cs3110.github.io&#x2F;textbook&#x2F;chapters&#x2F;data&#x2F;trees.html" rel="nofollow">https:&#x2F;&#x2F;cs3110.github.io&#x2F;textbook&#x2F;chapters&#x2F;data&#x2F;trees.html</a>
评论 #43832873 未加载
MJGrzymek14 days ago
sticking to dfs, there is still a difference between pre-order&#x2F;in-order&#x2F;post-order
thatguysaguy14 days ago
It&#x27;s not at the language level, but for python JAX has the notion of a pytree (arbitrarily nested combination of lists, dicts, and tuples), and includes map and reduce operations for those objects. It&#x27;s very convenient!
crvdgc14 days ago
<a href="https:&#x2F;&#x2F;hackage.haskell.org&#x2F;package&#x2F;base-4.21.0.0&#x2F;docs&#x2F;Data-Traversable.html" rel="nofollow">https:&#x2F;&#x2F;hackage.haskell.org&#x2F;package&#x2F;base-4.21.0.0&#x2F;docs&#x2F;Data-...</a>
评论 #43833202 未加载
qoez14 days ago
How in the world is this getting a HN hug of death when it&#x27;s a substack page?
f1shy14 days ago
Well common lisp has it.
SuperV123414 days ago
What you want is &#x27;yield&#x27; and generator coroutines. We have them in C++20, but their codegen is very poor compared to a manual implementation.
drbojingle13 days ago
What programming languages need is dimensional analysis. The fact that I can make variables like<p>Const km = 1 Const velocity = 11<p>And do km + velocity = 12 is all kinds of silly.
评论 #43845186 未加载
评论 #43845540 未加载
kazinator14 days ago
Here you go!<p>A TXR Lisp macro <i>for-tree</i> for traversing a tree, given a variable, an expression for the root node, and expressions for how to to left and right relative to the variable.<p>A node structure that <i>for-tree</i> knows nothing about. No iterator abstraction or API, nothing.<p><pre><code> (defstruct node () value left right) </code></pre> Example tree (a balanced binary tree whose numeric values happen to be sorted):<p><pre><code> (defvar example-tree #S(node value 5 left #S(node value 3 left #S(node value 1) right #S(node value 4)) right #S(node value 7 left #S(node value 6) right #S(node value 9))) </code></pre> Walk it:<p><pre><code> (for-tree nod example-tree nod.left nod.right (prinl nod.value)) 1 3 4 5 6 7 9 </code></pre> Code:<p><pre><code> (defmacro go-leftmost (var root left-expr stack) ^(for ((,var ,root)) (,var) ((set ,var ,left-expr)) (push ,var ,stack))) (defmacro for-tree (var root left-expr right-expr . body) (with-gensyms (stack node left right) ^(let (,stack) (go-leftmost ,var ,root ,left-expr ,stack) (for ((,var (pop ,stack))) (,var) ((iflet ((,right ,right-expr)) (go-leftmost ,var ,right ,left-expr ,stack)) (set ,var (pop ,stack))) ,*body)))) </code></pre> The <i>left-expr</i> and <i>right-expr</i> must be expressions that involve the <i>var</i> variable; the construct evaluates these to find the left or right child. When the value is <i>nil</i> it means there is no child.<p>This is almost exactly what the blog is asking for, transliterated to Lisp syntax.<p>Original concept in fantasy C syntax:<p><pre><code> for_tree(Node* N = mytreeroot; N != NULL; N : {N-&gt;left, N-&gt;right} { print(N-&gt;value); } </code></pre> Lispified:<p><pre><code> (for-tree nod example-tree nod.left nod.right (prinl nod.value)) </code></pre> As required, the construct specifies the node variable to be the loop dummy, the root expression giving its initial value and the two accessor expressions for left and right traversal.<p>The termination test N != NULL is made implicit in the Lisp version, so early termination requires a break out of the loop. It could be arranged.<p>The fantasy C syntax specifies a variable name in the navigation declarator: N : { N-&gt;left, N-&gt;right }. Presumably, the variable here can be renamed to anything you want; it just arbitrarily happens to have the same name as the loop dummy.<p>I didn&#x27;t replicate this feature exactly because it just adds verbosity for nothing. It&#x27;s okay if the navigation declarator just refers to the loop&#x27;s one and only dummy variable.<p>Anyway, programming languages should definitely not have a tree traversal primitive. Rather, languages should be Lisp.
评论 #43838612 未加载
fedeb9513 days ago
programming languages shouldn&#x27;t have such primitives, because they&#x27;re not primitive. Otherwise, people programming different stuff all the time, would need other primitives, and you&#x27;d have unmaintainable languages full of bugs.
rurban11 days ago
Of course not. You already have iterators.
eru14 days ago
Haskell (for example) doesn&#x27;t even have a looping primitive. Why would it have a tree traversal primitive?<p>Both looping and tree traversal can be done with library functions.<p>In general, everything that can be done with library functions, should be done with library functions.<p>&gt; Doesn&#x27;t for_tree(...) look a lot nicer and simpler and less error prone than needing to implement a recursive function for each operation you would want to do on a tree?<p>Eh, in Haskell you can derive many of these things automatically. And even Rust has some automatic derivation.
specialist14 days ago
FWIW, I use Null Objects to eliminate null checks. Makes recursion (tree climbing) more concise, legible.<p>Bonus: Java&#x27;s HotSpot magick will NOP (most?) methods of Null Objects, making this a zero cost abstraction.<p>I should probably write a concrete example for a blog or something.<p>TLDR: For every base class such as TreeNode, create a NullTreeNode that does nothing, then replace all uses of null with NullTreeNode. Voilá, no more null checks or NPEs.
colbyn14 days ago
It&#x27;s called sum types and pattern matching, and this is like 80% of the reason why I like Swift and Rust.
jdeaton14 days ago
Its called recursion<p>&gt; Doesn&#x27;t for_tree(...) look a lot nicer and simpler and less error prone than needing to implement a recursive function for each operation you would want to do on a tree?<p>No it does not
austin-cheney14 days ago
Operating systems have this with file systems.<p>Web browsers have this. It’s the DOM and it scares the shit out of most JavaScript developers. The fun part is confronting people about their irrational fears watching them spin their wheels with all the imagined excuses.<p>I am a tremendous fan of tree systems. Tree systems should completely replace inheritance in programming to enforce the composition over inheritance principle.
Mikhail_K14 days ago
Ordering pizza is much more important and frequent part of a developer&#x27;s work than tree traversal. Therefore, programming languages need pizza ordering primitive even more, than tree traversal ones.
chess_buster14 days ago
Nonsense.<p>Programming languages should have less primitives like this and instead we should have better foundation libraries for the languages, i.e., containing iterator&#x2F;-interfaces like Rust and Python (or Smalltalk).
Nerdx8612 days ago
My concern here is what a recursive primitive like this in a low level language (like C&#x2F;C++&#x2F;Zig&#x2F;Rust) would imply to the execution context of the &quot;code within the brackets&quot;. If it was within the context of a lambda, etc that&#x27;s one thing.<p>I will be transparent here, I do not know of a non recursive, tree traversal alogorthim that uses constant storage but doesn&#x27;t modify the tree.. (Morris Traversal satisfies 2 but makes temporary changes to the tree)<p>Most primatives do not allocate hidden data (even C++ iterators require you to instantiate the iteration state as a variable) and operate within a known stack&#x2F;execution context.<p>Having an actual primitive that implements arbitrary tree traversal would require a substantial complier generated, but non-transparent, framework for that execution. It would either need to be recursive, have localized dynamic data, or would require tree structures that are well defined to use something like Morris. (And Morris is natively non-concurrent even for reads)<p>While this means that that simple primitive would actually do a lot of potentially problematic things under the hood. How many Junior programmers would call it concurrently for reads because it&#x27;s not &quot;modifying the data&quot;. Why do you need to lock? Things like brake and continue and other control mechanisms wouldn&#x27;t actually work as assumed from the flat context.... I.E what happens if somebody called a goto inside of that block? Also, in an embedded context where you may not have a dynamic allocator, how would a version written as in reiterative function with a expanding stack data structure function? From a language standard point of view, there are so many complexities to building something like this that runs in any context that the language is supported. Something like this that runs in any context that the language is supported.<p>I mean look at C++, they have specifically and (deliberately &#x2F; arguably rightfully) failed to build copy on write strings (a simpler abstraction) because there are cases where they couldn&#x27;t guarantee that they would have the expected results in all contexts.<p>This to me sounds more like something that should be a lambda function and a standard library, not a primitive... Languages that support dictionaries and maps as part of their standard data structures already have library support for tree traversal internally. They have solved many of the problems of making that language exposed, so that would be a much smaller ask. And programmers rightfully assume a lot more complexity within library calls then they do within primitives. Primitives are designed to be primitive. Having primitives able to create stack frames, dynamically allocate hidden memory, and&#x2F;or recurse honestly worries me.
meindnoch14 days ago
Horrible idea.<p>1. BFS is not supported.<p>2. Traversal type (inorder, preorer, postorder, or even mixed order!) is also not handled.<p>3. The syntax doesn&#x27;t make it apparent that stack overflow can occur, e.g. by doing DFS on a linked list.
danieloj14 days ago
As a fullstack web engineer I&#x27;ve never had to implement a tree structure at work. I&#x27;d love to hear examples of what kinds of companies&#x2F;platforms people are writing these structures regularly. If anyone is willing to share where they use them I&#x27;d appreciate it
评论 #43843313 未加载
评论 #43836365 未加载
评论 #43836466 未加载
评论 #43837299 未加载
评论 #43838567 未加载