6 ms·
In the example above, you first have a list of books. Then you filter it down to books with >1000 pages. Then you map it to authors of books with >1000 pages. T
by jltsiren 2y ago
In the example above, you first have a list of books. Then you filter it down to books with >1000 pages. Then you map it to authors of books with >1000 pages. Then you collapse it to distinct authors of books with >1000 pages. Every step in the chain adds further complexity to the description of the things you have, until it exceeds the capacity of your working memory. Then you can no longer reason about it.
The standard approach to complexity like that is to invent useful concepts and give them descriptive names. Then you can reason about the concepts themselves, without having to consider the steps you used to reach them.
- lelandbatey 2y agoFolks who are familiar with chaining don't think about it in the way that you've presented. If you're familiar, it's more like: Filter to the books with >1000 pages Then their authors. Finally, distinguish those authors. If you're familiar, you don't mentally represent each link in the chain as the totality of everything that came before it _plus_ whatever operation you're doing now. You consider each link in the chain in isolation, as its inputs are the prior link and its outputs will be used in the next link. Giving a name to each one of those links in the chain isn't always necessary, and depending on how trivial the operations are, can really hurt readability. I think its very much a personal preference.
- jltsiren 2y agoThe problem with that is that it's all implicit. If the steps are sufficiently complex and if you don't already know what the code is doing, you don't always have a clear mental image of what the intermediate state after each step is supposed to represent. And with a chained syntax like that, you don't have an option to give the intermediate state an explicit name. A name that could help the reader understand what is going on. You don't have to give a name to every intermediate state, just like you don't have to comment every single line of code. But sometimes the names and comments do improve readability.
- yxhuvud 2y agoI have a guideline where I tend to put a name on a result if and only if it changes the type of the data compared to the previous step. It works well.
- stouset 2y agoSo just do that then in the cases where you think it improves clarity? It's not like you can't assign names in the functional style if you need to.
- bluGill 2y agoThe problem is the "you" in question is not always able to. When "you" write code it makes sense and so you don't need to assign many names. The you in six months will want more names, and in 6 years that will be different again (how many depends - if this code is changed often then you know it much better than if it has been stable). The worse case will be after you "get hit by a bus" and the "you" in question is some poor person who has never seen this code before.
- stouset 2y agoUnlike the procedural approach, every step in a functional chain is wholly isolated and independent from the others. It is strictly easier to split this style of code up into two halves and name them than it is to disentangle procedural equivalents. I have quite literally zero times in my ~25 year career had to deal with some sort of completely inscrutable chain of functional calls on iterators. Zero. I am entirely convinced that the people arguing against this style have never actually worked in a project where people used this style. It's okay! The first time I saw these things I, too, was terribly confused and skeptical.
- bluGill 2y agoI will admit to not having written any significant functional code. However the poster child for functional programming always seems to be small programs (xmonad is the largest one I can think of, and the procedural counterparts are not that big either. Of course there is a lot of code out there that nobody can talk about). Thus I have to conclude the question of how that style scales to really large programs remains open. That said, you didn't address my comment at all. It might be easier, but that doesn't mean it is easy to figure out what that long chain is really done - all too often the algorithm names don't tell you what you are really trying to accomplish in my experience.
- stouset 2y agoYou have literally just described the set of objects asked for: the unique authors of the books with more than 1,000 pages. I don't understand how you expect to get any simpler than that. The functional style isn't even requiring you to describe how to accomplish it, it almost verbatim simply describes the answer you're trying to get. If your entire objection is that you might want intermediate-named variables… you can just do that? var longBooks = books.filter(book => book.pageCount > 1000) var authors = longBooks.map(book => book.author) var distinctAuthors = authors.distinct() For short chains (95%+ of cases), this is far more mental overhead. For the remaining cases, you can just name the parts? I'm just completely failing to see your problem here.
- jltsiren 2y agoThe problem is that it's easy to overdo it. When you are writing the code, you already know what it's supposed to do, and adding a few more things to the chain is convenient and attractive. But when you are reading unfamiliar code, you often wish that the author was more explicit with their code. Not just with what the code is actually doing, but what it's trying to do and what are the key waypoints to get there. With procedural code, it's widely accepted that you should not do too many things in a single statement. But in functional code, the entire chain is a single statement. There are no natural breakpoints where the reader could expect to find justifications for the code.
- stouset 2y ago> The problem is that it's easy to overdo it. Welcome to all features of every programming language? Sacrificing readability, optimization, and simplicity for the 95% case because some un-principled developers might overdo it in the 5% case (when the cost of fixing it is trivially just inserting variable assignments) is… not a good trade-off.
- jltsiren 2y ago5% is common enough that you'll encounter it almost every time you read code. And fixing it is not easy, because you first need to understand the code before you can add useful variable names. Besides, programming language evolution is mostly driven by the fact that everyone is lazy and unprincipled at least occasionally. If you need to be disciplined to avoid footguns, you'll trigger them sooner or later.
- jayd16 2y agoEach predicate is a separate scope. How is the complexity additive? If you really have to you can simply be just as specific in your predicate naming as you would in a for loop. var authorsOfLongBooks = books .filter(book => book.pageCount > 1000) .map(longBooks => longBooks.author) .distinct()