Fake Trees: Using Indents for Simpler UIs
ratfactor.com
ratfactor.com
The second (the 'vastly simpler method') i don't recall seeing before. It has some fairly obvious deficiencies, but it is clearly enough in some cases.
The third ('namespacing') is called a materialized path.
And there is at least another way to represent trees - nested sets: https://www.ibase.ru/files/articles/programming/dbmstrees/sq...
All of these were well-trodden back in the days when people took relational databases seriously. For example, see: http://www.dbazine.com/oracle/or-articles/tropashko4/
It seems this is lost knowledge now.
It’s really hard, in my experience, to come across the existing name for something while you’re still figuring out all the angles of a problem for yourself.
When I work on a problem that’s new to me, I ask around, explaining the problem, checking if someone recognizes the domain, if it is known.
When someone explains to me what I’m working on is a solved problem, I take great joy from it since I already understand the issue some, I can criticize or take great energy from learning something for sure.
I believe how to feel about it is a choice btw. As movie says “Always Look on the Bright Side of Life”.
Also I end up getting to meet programmers from different projects/departments that I would have never met if I was only working in office.
I assume most problems I face are solved, and that I just don't know shit. So I scramble to research the solutions, and I'm shocked when what seem to be widely-encountered problems are NOT, in fact solved.
One recent example I encountered was API definition. I was tasked to define a new API for my company's products, and celebrated when I discovered OpenAPI. I was a bit surprised to find that only the very latest version of the standard (3.1) was competent enough to be useful.
And despite 3.1 being ratified for years, today there are still no usable code-generation tools that support it. I wasted weeks studying and trying to fix various tools, after studying reams of redundant and conflicting documentation in different repositories... thinking it was my problem. No. It's just a hideously broken mess.
Today I'm dealing with the same thing in SwiftUI... and again have reached the conclusion that the programming paradigm it pushes has not been thought through. Its rushed and immature state shows in its kitchen sink full of overlapping and rapidly-deprecated approaches to problems that were solved in traditional application structures a decade and a half ago.
Just typing that out, I wonder if I failed to learn from my first example and wasted too much time on the second. But if you're a thorough person, you have to satisfy yourself that you've been diligent in trying to inform yourself of best practices.
On the other hand, I find a lot of bugs. Not in things as refined as compilers, usually, but if I had a dollar for every time I've heard, "Well, nobody else has reported this before," I could probably buy a tank of gas. And that's saying something today.
Latest example: Amazon's Web site rejects all phone numbers entered on an Apple Silicon Mac. I was setting up a new address, and it said "remove invalid characters from phone number field:" https://i.imgur.com/mjwiCqc.png
This happened in both Safari and Firefox. Amazon support couldn't figure it out in 1.5 hours of chat. I went to an Intel Mac and entered the exact same number, no problem.
I like neocities.org for small experiments like this but you can use anything you’d like.
I don't know what else could vary based on platform. One thing I can't control for is browser and OS versions, because the older Mac I have (the one that works in this case) can't be updated to the latest.
It must be sending garbage characters. But the vast, vast majority of sites work. It's really mystifying.
As far as other sites are concerned... I just remembered one that's not quite as clear-cut. Zoro.com complains that my credentials are wrong, but if I reset my password it complains that I'm trying to use my current password... so it's obviously not wrong. As usual, they just threw up their hands... and sent me a 10% coupon... which of course I can't use because their site is broken. Just tried it again, and it's still unusable. Didn't try the older computer yet.
Absolutely! Discoverability is a huge challenge. There is so much amazing work that's been done in the past that is virtually impossible to find out about.
I only know this stuff because I was working with databases fifteen years ago and an older colleague encouraged to me to learn more about it.
Of course, you still have to do all the work of describing the problem. But if you iterate through your problem and solutions within a conversation you can get to what you need (a conventionally understood term) faster than writing a fully-fledged blog post.
I copied the context of the article into ChatGPT (v4) and asked it "What are the names of these methods that are conventionally understood in the wider industry?"
It suggested terminology like: "adjacency list", "materialized path" and "nested set".
https://chat.openai.com/share/3677c2f9-f844-469b-84a1-905840...
Overall this is healthy, a new set of eyes to an old problem can yield new solutions. In some cases though, it's a facepalm situation. Like the whole Javascript world of today.
I knew I was reinventing the wheel but I also knew the learning results would be immense. I usually work in real-time systems and it was great to have a project to learn how to do offline data-in data-out types of problems. The two types of problems can be very different.
Also it gave me a good excuse to finally actually do something with a functional language.
I'd never recommend my static site generator to the general public though.
Someone somewhere decided they didn't like SQL and engineered a way to postpone having to know SQL until their ORM performance dragged their systems to a halt.
The focus on micro services to, probably has an impact - the back end can be changed if your database is having issues and the services stay the same.
Also databases are hard and a large chunk of developers only know javascript and html.
You don't see many DBA jobs any more, a lot of that is moving to the cloud so they just pay more money and throw CPU's at the problem rather than optimise the database.
Also agile/scrum (as its practiced anyway) doesn't really allow for a database design up front as it used to be done.
1. The whole noSQL thing
2. A move away from databases as store of all knowledge to databases as implementation detail of applications, which IMHO was a good thing, but tended to make them smaller, simpler and more hidden away
https://www.oreilly.com/library/view/joe-celkos-trees/978155...
You nailed it. We hire younger grads who just want to cram everything into a nosql document and not give any thought to data modeling. Of course, they do all the logic of displaying the tree in code, which is sad as modern relational databases along with some CTE’s can handle so many use cases for free and in an elegant fashion.
https://www.postgresql.org/docs/current/ltree.html
For instance:
CREATE TABLE test (path ltree);
INSERT INTO test VALUES ('Top');
INSERT INTO test VALUES ('Top.Science');
INSERT INTO test VALUES ('Top.Science.Astronomy');
And then a simple search with: ltreetest=> SELECT path FROM test WHERE path <@ 'Top.Science';
path
------------------------------------
Top.Science
Top.Science.Astronomy[1] https://learn.microsoft.com/en-us/sql/relational-databases/h...
Put another way, ltrees are a denormalized representation; we denormalize to optimize lookups. Other approaches that use foreign keys and such are more normalized representations; we normalize to give ourselves the power to enforce constraints and to optimize update operations. But as long as we can trade some storage and write amplification for performance on lookups, we aren't tied to one approach or the other, we can mix and match.
In case you ever work with an Anatomist, and they are visibly freaking out at you destroying everything it is because they order parts anterior to posterior and it is sooooooo obvious they wont ever feel the need to mention it ...
A concern is that json indexes might not work as well as ltree indexes
So whilst the labels in ltree values do imply a logical tree, by describing materialized paths through it, they do not enforce the presence of records matching all the implied parent nodes.
Depending on your application this may be exactly what you wanted, or exactly what you didn’t want. In the latter case, you’re gonna need to put something around it to maintain integrity.
IMO, it's a dangerous thing to hold VISUAL information in a data structure in a database. Seems shortsighted.
What's the response here? "No, that can't possibly be true"?
You Aren't Gonna Need It is a celebrated building heuristic for a reason. You Should Always Assume You Need It simply isn't true.
I'm not talking about learning how to balance a binary tree in the most efficient way possible. I'm talking about the basic ops of breadth- and depth-first traversals of arbitrary trees.
I feel like we live in an era of software development that took a stupid meme about "inverting a binary tree" as a job interview question and--not knowing anything about trees--turned it into received wisdom that "trees are hard".
But they just aren't. And yet so many people live in fear of them and go to great lengths to avoid having to learn how to use them.
So I think the easiest way would be to store as order/indent first and later migrate to parent/child when you implement features that need it.
The only change I'd do is to define "indent" more abstractly as the item's depth in the tree and not literally the number of spaces you want to render. That makes it a lot easier to spot invalid data, makes later migration easier - and also gives you more UI flexibility, because it allows you to change the rendering method or make it configurable per user (nested <ul>'s/<ol>'s, tabs, 8 spaces, 4 spaces, 1 space, etc)
struct item_t {
char key[255];
char display_value[255];
}
And your key has consistent path separators like a/b/cThen looking up parents and children is incredibly easy. At worst it's a linear scan of the array, but if it's in order you can even just look at previous items in the list until you're at the parent.
Once you understand those concepts, then storing your data correctly as trees has a ton of benefits over indenting like this.
Of course the average dev won't do that, they'll just add a hacky workaround as they always do and end up with a buggy horrible mess but thats irrelevant. They'll do that anyway if they're that type of dev.
And honestly I despise this "managers are short sighted" excuse. We're the developers, we do the work. If I open a repo and see a horrible buggy mess with your name on it I'm judging you, I don't give a crap who your manager was.
I promise you that CTEs, even recursive ones, are not scary and that once you get the hang of them are actually fun.
I've been noticing that this is one way in which HN and Reddit differ. On HN, a child comment is a nextSibling of the parent comment, with indent set to parent's indent plus one to simulate the appearance of trees. On Reddit (or at least old.reddit.com, don't know about the new site), a child comment is actually nested inside the parent comment.
While "the way it looks" might be all that matters to most users, perhaps there are cases where semantic markup is critical (screen readers? browser extension development?).
[0] https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSi...
The narrative, though, is imho faulty. You dont need CTE to retrieve the tree from the db - you can fetch a flat list and construct the tree locally, as you would most likely need to anyway for following manipulations.
The very same argument can be made about people who use rdbms to store the list - store it in a text file. Why pay the network latency cost?
Or alternatively, the proposed structure would not work with sufficiently big tree if we wanted to move branches around, changing their depth. That would impose linear cost.
State your intention at the beginning, instead of explaining three different examples that are later on invalidated in the conclusion section with "if you need a tree, use a tree". But adding this to the beginning of the article would be way less clickbaity.
https://www.oreilly.com/library/view/joe-celkos-trees/978155...
I structure things as trees because I want to be able to traverse them. When a user sees your tree and expects to be able to do tree operations on it, but discovers its just an illusion and they can do no such thing, then dissatisfaction sets in.
> And for the love of all that is good, if you actually need a tree, use a tree.
It’s in almost every section.
Also, it's mentioned at least three other times in the article before the conclusion. Just one example: "Do your items actually have to have a parent-child relationship, or do they just need to look like they do?" That's pretty clear.
But it also goes on with multiple examples that support their approach whilst neglecting storing data as real tree ("That sounds like a lot of work and potentially awkward-to-work-with data structure, especially if stored in a database" and "one way to get tree-structured data from a relational database with a SQL query is to write a recursive CTE (Common Table Expressions), which are just as fun as they sound"). You don't prove your point by using the most extremely examples of the "bad" approach.
The article is very vocal about shortcomings of storing trees as trees, but quiet about possible issues with the approach used.
It looks more like a emotional rant rather than an informative piece.
That's the whole purpose of the article. To neglect storing data as a real tree, hence the fake in the title.
It couldn't be more clear about it.
>It looks more like a emotional rant rather than an informative piece.
It gives descriptions and examples of several techniques, which will do fine for many simple use cases. Which is exactly what it promises.
This reaction seems more like an emotional rant rather than TFA.
In the namespacing example, it's trivial: the parent is the version of the name of the current node with one "namespace" less. E.g. /bar/boop/bleep's parent is /var/boop.
Similarly, the children of /bar are any nodes starting with "/bar/".
[1] https://www.get-plume.com/
[2] https://doc.qt.io/qt-6/qabstractitemmodel.html
[3] https://doc.qt.io/qt-6/qabstractlistmodel.html
[4] Q_PROPERTY(unsigned int indentLevel READ indentLevel WRITE setIndentLevel NOTIFY indentLevelChanged)
Also, tangentially, I am reminded of the array representation of the binary heap structure. An array-based binary heap maintains (as far as I am aware) the same properties as a pointer-based binary heap, but the structure is entirely implicit based on index.
There's a lot of ways to skin a cat for sure. Reaching for pointer-based trees of objects is of course totally fine, but there's a lot of ways to normalize data, and if you have a decent idea of what you're looking for you can definitely save a lot of trouble.
I used this for the table of contents on my personal site:
Parser: https://github.com/paradox460/pdx.su/blob/main/lib/toc.ex
Renderer: https://github.com/paradox460/pdx.su/blob/main/lib/layouts/p...
https://en.m.wikipedia.org/wiki/Nested_set_model
https://stackoverflow.com/questions/5368299/hierarchical-dat...
this can be used to make these fake trees while avoiding the adjacency structure that's harder to query.
What distinguishes a Foo from a Bar? Are 1, 2, and 3 (or I, II, etc.) actual data values, or simply row numbers arising from a sort on something else?
If you're writing the back end for OmniOutliner, and you must use a relational database, then maybe you'll use tricks like these. Otherwise, I would see whether you can come up with a schema that better models your data.
Making any changes to the order or grouping is wrought with peril because you never know if everything is going to be updated properly. When it isn't, it's an ordeal to correct.
Having a parent id and sort order is straightforward and about as simple as it gets. I really don't see why you'd need to do it any other way outside of a thought experiment.
Indeed - you're absolutely right.
Not sure if the comment I responded to was edited to add the parent node after my post or if I just overlooked it, but I should have quoted that I was just referring to the left and right siblings (i.e. two linked lists):
> an old cms that stores the id of the ... left sibling, and right sibling.
Use a transaction.
A few mkdir's followed by the tree command. Paste it into email, slack, pastebin, etc and ship it off. Takes less than two seconds and gets your point across better than verbally explaining.
interesting note : using the dot naming is also how the note taking VsCode extension Dendron handles names hierarchy. The file foo.bar.md is considered a subnote of foo.md
A soon as someone asks for <thing that requires the parent-child relationship that is implied to already be there>, then you have to admit that it's a smoke-and-mirrors trick and you can't actually achieve that with the data you have.
Avoid storing visual figments in your database, they'll come back to haunt you.
Nobody would store "Foo 1 a" as name of the deepest item, they would want to build the name dynamically based on the concatenation of the parents "Foo" and "1", in which case the system doesn't work at all.
Otherwise, most operations on the structure are impossible to perform (a simple rename in the middle of a branch).
Disingenuous article.
I think the benefit could mostly be for the implementation of the GUI to change the tree (that they do mention).
If you don't feel like using a hack, don't. (They make that clear, too.)
In the fake plaaaaaaaaaaaaaaaaaaaastic earth