Re:Anecdotally, Programmers Dislike "Reduce"
by eimrine on Monday September 14, 2026 @11:01AM
(#49698096)
Reduce requires knowing that the sum of zero entities is zero but the multiply of zero entities is one. They forget to throw the correct number and think that reduce() just do not work for them.
Re:Anecdotally, Programmers Dislike "Reduce"
by theamk on Monday September 14, 2026 @01:41PM
(#49700854)
At least in Python, I've found that "reduce" is very rarely needed. Most of the times, "sum" is enough, sometimes with "start" values customized (set it to [] to flatten an array for example). It is both easier to read, faster, and needs no imports. It also works great with list comprehensions - "sum(foo(x) for x in input if x > 5)" is much easier to read than reduce equivalent.
If you are multiplying, you are likely doing heavy math, and you'll be using numpy - which does not need reduce either.
If you are going to return a list of dict, then it's much faster to mutate the results, so using "reduce" will have significant performance implications (unless you want to return input argument, mis-using it as a glorified "for" loop)
And if returning not a list/dict, if you can use "min" or "max" or "any" or "all" or "next" (take the first element), then you should use it - it will be easier to read and faster too.
So what does this leave us for "reduce"? Frankly, not much. I've only seen it in merging immutable status codes, and that was pretty niche usecase to begin with.
(this was all for Python. In other languages without nice list of built-ins reduce might make more sense)
Re:Anecdotally, Programmers Dislike "Reduce"
by rsfern on Monday September 14, 2026 @11:03PM
(#49707205)
For numerical code I like einops.reduce more than numpy/pytorch sum reductions because you can reduce over named dimensions. It’s much more readable than having to reason through axis indexing again every time you come back to the code
Re:Anecdotally, Programmers Dislike "Reduce"
by Pinus on Tuesday September 15, 2026 @10:03AM
(#49712688)
Has the performance of sum on lists of lists in Python been fixed? It used to be pretty abysmal. But I suppose some would say that if you need to consider performance at all, you’re in the wrong language… :)
Re:Anecdotally, Programmers Dislike "Reduce"
by theamk on Tuesday September 15, 2026 @11:25PM
(#49721763)
wow, TIL!
Python 3.13.5 (main, Jul 15 2026, 20:25:40) [GCC 14.2.0] on linux
>>> x = [[n]*1000 for n in range(1000)]; import timeit, itertools, functools, operator
>>> timeit.timeit("len(list(sum(x, [])))", number=10, globals=globals())
13.009033881127834
>>> timeit.timeit("len(list(list(functools.reduce(operator.add, x, []))))", number=10, globals=globals())
12.941937348805368
>>> timeit.timeit("len(list(itertools.chain.from_iterable(x)))", number=10, globals=globals())
0.0706032607704401
>>> timeit.timeit("out=[]; [out.extend(i) for i in x]; len(out)", number=10, globals=globals())
0.06334403157234192
>>> timeit.timeit("len([i for a in x for i in a])", number=10, globals=globals())
0.1232151910662651
mutable is fastest, itertools is just a bit slower, list comprehension is 2x slower, both "sum(..., [])" and "reduce" are 200 times slower!
Re:Anecdotally, Programmers Dislike "Reduce"
by sigbottle on Wednesday September 16, 2026 @03:18PM
(#49731614)
It always messes with me: reducing across a specific axis always takes O(whole tensor) time, because there's no difference between "iterate over all dims, then collapse the final one" versus "iterate versus the first dim and do some cursed tensor accum" (and likewise for between)
Maybe there's just a better way to think about it and I'm still thinking about it way too much like a programmer
Re:Anecdotally, Programmers Dislike "Reduce"
by KingMob on Thursday September 17, 2026 @06:05AM
(#49738572)
But that's not actually guaranteed at all.
You "accumulate" an answer one item at a time, but there's no guarantee any dimensions are getting reduced.
You can easily duplicate the effects of map with reduce, for example, so the dims would stay the same. You could even expand dimensions, if you like, turning a 1-d array with n elements into an s X t 2-d array. If the reducing function tracks the total number of elements seen, it can easily know when to start a new row.
This is part of why people keep pointing out the name, "reduce", is a bit misleading.
Re:Anecdotally, Programmers Dislike "Reduce"
by billyp-rva on Monday September 14, 2026 @03:37PM
(#49702709)
Well yeah, it's the lowest-level array function. All of the others can be written with reduce, but not vice-versa. Of course it's going to be less friendly.
Re:Anecdotally, Programmers Dislike "Reduce"
by slopnt on Monday September 14, 2026 @03:55PM
(#49702991)
I like reduce in principle since it generalizes a simple concept pretty nicely. I don't use it that much in practice since its alternatives just require less brainpower. It competes against using local mutable state with a loop or iterator combinator which I would argue are easier to wrap your head around (i.e. loop with variable/map with closure). I would argue its one of those cases where something is just harder to do/understand in functional vs imperative programming.
Re:Anecdotally, Programmers Dislike "Reduce"
by japgolly on Monday September 14, 2026 @08:53PM
(#49706335)
I assume the author is talking about `fold`, as in `[A] -> B -> ((B,A) -> B) -> B`, and not what I often think of as reduce as `[A] -> ((A,A) -> A) -> A`.
`fold` is awesome and super useful. It's the easiest and most convenient way to turn a collection into a single value. Put me anecdotally in the opposite bucket.
Re:Anecdotally, Programmers Dislike "Reduce"
by t-3 on Wednesday September 16, 2026 @02:23PM
(#49730955)
There are way more places where a simple typo will ruin you in a for loop than a reduce or fold or map. Using briefer abstractions in place of nested loops is almost always preferable.
Re:Anecdotally, Programmers Dislike "Reduce"
by mrkeen on Wednesday September 16, 2026 @02:32PM
(#49731060)
Nah. It involves multiple passes and setting the answer to the wrong value before (hopefully) setting it to the right value.
Plus it forces you out of whatever lazy/streaming paradigm you had going on. If your foldr produces a list, downstream can start consuming it in constant memory as long as you let it do its thing.
Re:Anecdotally, Programmers Dislike "Reduce"
by 8note on Wednesday September 16, 2026 @02:39PM
(#49731142)
fold kinda does too, for setting the first combined value that you are assembling, and thus on an empty list you end up with that wrong value, same as the for loop
Re:Anecdotally, Programmers Dislike "Reduce"
by KPGv2 on Thursday September 17, 2026 @10:44AM
(#49741572)
If your data should not be successfully folded if it's empty, you should've already parsed it as an Optional nonEmptyList instead of letting illegal states fly around for a while in your application.
Re:Anecdotally, Programmers Dislike "Reduce"
by bspammer on Thursday September 17, 2026 @11:46AM
(#49742533)
Yeah that's good application design, but those concerns aren't so relevant to the person writing the standard library for a language. You can certainly include a specialized version of reduce for nonEmptyLists which just returns A, but that doesn't change the fact that you have to return an Optional for a normal possibly-empty iterator if you want your reduce function to be non-partial.
Re:Anecdotally, Programmers Dislike "Reduce"
by KPGv2 on Thursday September 17, 2026 @10:39AM
(#49741490)
For loops are not easier or more convenient than fold.
fold sum 0 collection
versus
acc = 0
for x in collection:
acc = acc + x
or the even worse
int acc = 0;
for(int x = 0; x < collection.length; ++x) {
acc += collection[x];
}
You can read one line and know exactly what's happening in the fold example. In the Python and C++ examples, you have to scan more lines and there's way more opportunity for typos.
A for loop gives you better memory management and speed, but the tradeoff only makes sense to me if you're doing embedded work or something. Otherwise, eat the .000000000001% speed loss to reduce the risk of logic errors, typos, etc. and to improve developer ergonomics.
Re:Anecdotally, Programmers Dislike "Reduce"
by wannabe44 on Thursday September 17, 2026 @11:04AM
(#49741882)
Only real difference is that `fold` is denser. Both require prior knowledge to understand in their respective paradigms.
Adding numbers like this is not common in real world code. Now let's say instead of adding x, you have too look up X in a cache with an additional "type" param and update a metric of cache hits (or misses). You have to define a free function to keep your fold readable and understandable. In for loop it's much easier to understand.
Re:Anecdotally, Programmers Dislike "Reduce"
by KPGv2 on Sunday September 20, 2026 @07:50PM
(#49781386)
>You have to define a free function to keep your fold readable and understandable.
I agree. But you'd do that for a for-loop, too, unless you want a bloated for-loop.
> In for-loop it's much easier to understand.
I have to disagree there. You'd still be working with a free function, or you'd be working with a bloated for-loop body.
Combining cache loopkups, metric tracking, etc. runs into SOC issues that IME for-loops just let imperative developers get away with until it comes time to test their code.
Furthermore, free functions aren't bad. They're good. They're a self-documenting abstraction. Unless you name it `function_one` or something.
Having my fold lambda do its primary business role but call `update_cache_and_metrics` makes it unnecessary for someone reading the flow of logic from even needing to go read the body of that free function.
Re:Anecdotally, Programmers Dislike "Reduce"
by nightpool on Wednesday September 16, 2026 @03:00PM
(#49731400)
I think part of the issue is that a lot of programming languages don't make a strong distinction between the two, and only provide the (more powerful) fold, but in a way that makes reduce operations harder to reason about (like OP said, with 0 types).
Associativity also makes fold hard. It's not super trivial to know when you might need e.g. left fold vs right fold
Re:Anecdotally, Programmers Dislike "Reduce"
by Chinjut on Wednesday September 16, 2026 @03:09PM
(#49731518)
These are pretty close to each other, to the point where I wouldn't bother strongly distinguishing them.
Suppose we have foldr as in [A] -> B -> ((A, B) -> B) -> B, foldl as in [A] -> B -> ((B, A) -> B) -> B, and reduce as in [A] -> ((A, A) -> A) -> A.
Then we have foldr list value operator = reduce [\b -> operator a b | a <- list] (.), foldl list value operator = foldr (reverse list) value (flip operator), and in the case of a finite non-empty list and associative operator, we have reduce list operator = foldr (tail list) (head list) operator = foldl (init list) (last list) operator.
So these are all basically slight re-parametrizations of each other.
Re:Anecdotally, Programmers Dislike "Reduce"
by s-zeng on Tuesday September 15, 2026 @12:44AM
(#49707808)
Even in the world of functional programming, there's an argument to be made that `fold` is a bit of a code smell, in a similar vein as `while` being slightly smelly in an imperative code base. There's good reasons for each to be used, but they are such low level iteration primitives that you might be better off with a higher one (e.g. for loops or iterators in imperative programs; in FP you might reach for monoidic reduces (as opposed to folds where the accumulator is a different type from the list element), monadic traverses, or recursion schemes). Even though you can implement iterators or for loops in terms of while loops, you probably shouldn't, and similar for functional traversals.
In languages like python or Java though, you don't really have access to many of the higher power functional traversals however. So that puts you into a similar kind of bind as working in a language with only while loops
Re:Anecdotally, Programmers Dislike "Reduce"
by theamk on Wednesday September 16, 2026 @03:56PM
(#49732089)
I assume OP refers to the cases where "while" is used to re-implement existing operations... imagine finding code like this:
i = 0
while i != len(todo):
process(todo[i])
i = i + 1
sure, there may be a good reason to implement things this way (maybe "todo" grows during iteration?), but maybe not, and then the loop should be instead simplified to:
for value in todo:
process(value)
(as an aside, this is exactly the case where the comments are required: "# not using for loop because todo might grow" will make it clear it's an intentional decision and not hallucination or something written from ignorance)
Re:Anecdotally, Programmers Dislike "Reduce"
by theamk on Thursday September 17, 2026 @01:07PM
(#49743630)
you mean like that?
todo_iter = iter(todo)
while True:
try:
value = next(todo_iter)
except StopIteration:
break
process(value)
or like that?
todo_iter = iter(todo) # Note: assume "todo" does not contain None
while value := next(todo_iter, None):
process(value)
I'd say neither of those are as clean as a simple for loop:
for value in todo:
process(value)
and yes, that's the case of a personal preference, although I'd bet a lot of Python programmers will share that preference with me. That's what "code smell" means, after all - it's not a bug which is clearly incorrect, it's a code which is best avoided based on reviewer's personal experience.
Re:Anecdotally, Programmers Dislike "Reduce"
by grebc on Thursday September 17, 2026 @02:55PM
(#49744998)
Your for loop is using an iterator of some kind. Just because it’s hidden in your language of choice doesn’t mean it’s not there.
While/for can achieve the same thing, sometimes while is more practical as the steps to complete are unknown. But sure, stick your simple iterating a fixed collection as why it demonstrates while is a lesser language feature.
Re:Anecdotally, Programmers Dislike "Reduce"
by theamk on Friday September 18, 2026 @04:10PM
(#49759581)
I think your arguments would be much stronger with some actual code samples.
Usually, when the same code can be written either as range-based "for" or as a "while", the "for" will look better and have fewer possibility of bugs. If you have examples otherwise, I'd like to see them.
(Note I am specifically talking about range-based/iterator-based "for", not the C's variant. Nor am I talking the cases where the "for" is hard to use, like when the size might change at runtime)
Re:Anecdotally, Programmers Dislike "Reduce"
by d--b on Thursday September 17, 2026 @12:31AM
(#49736441)
Probably because while is the source of many infinite loops, and because it’s sometimes faster and more rigorous to compute the length ahead of going into the loop.
That said, I personally don’t think it’s smelly at all.
Re:Anecdotally, Programmers Dislike "Reduce"
by snackbroken on Tuesday September 15, 2026 @01:42AM
(#49708207)
Map and Filter are nice because they let you reason locally about a single element in isolation. Reduce(Fold) forces you to reason globally about intermediate results. Reduce also forces you to conjure up a "zero" value of the relevant type, which isn't usually difficult but it does constitute some extra mental overhead.
Re:Anecdotally, Programmers Dislike "Reduce"
by snackbroken on Wednesday September 16, 2026 @02:52PM
(#49731319)
The accumulator is global state. If you're folding from list<int> to int you're right that it's (usually) effectively a pairwise operation on ints. If the fold is something like list<foo> -> tree<bar> then you have to reason about each intermediate (tree<bar>, foo) -> tree<bar>, i.e. how global state should evolve over time with each update.
Re:Anecdotally, Programmers Dislike "Reduce"
by snackbroken on Wednesday September 16, 2026 @04:31PM
(#49732571)
That's what I'm used to as well, but in my experience a lot of programmers take fold and reduce to be synonyms. A monoidal reduce is much less "scary" than a general fold. I suspect most programmers have never[1] heard the word monoid, let alone know what it means, and having to remember the meaning of a weird new word is enough to make most people dislike something compared to the simpler more familiar operations.
[1]Or if they have, their only encounter with it is the "a monad is just a monoid in the category of endofunctors" meme.
Re:Anecdotally, Programmers Dislike "Reduce"
by ndriscoll on Wednesday September 16, 2026 @09:27PM
(#49735326)
It just means if you have some functor F (generic type with a well-behaved `map` function, like List), then you have a `flatten` operation F[F[_]] - > F[_], and like a monoidal product, it's associative. So if you have a triply nested List, you can flatten inside first or outside first. Also, like a monoid, it has an "identity" function wrap: A->F[A] (e.g. x -> [x]). Identity in the sense that "multiplying" (flattening) with wrap does nothing. i.e. wrap(flatten(x)) = flatten(wrap(x)) = x when those things make sense.
So basically wrapping and flattening behave in a sane way. Flatten is your multiply, wrap is your multiplicative identity, and it's like a monoid if you squint.
Re:Anecdotally, Programmers Dislike "Reduce"
by ndriscoll on Thursday September 17, 2026 @12:59PM
(#49743553)
"The meme" literally comes from a book that was offering it as an intuitive explanation of a long definition, assuming you know what a monoid is. All the laws and stuff boil down to "if you generalize the idea of a monoid a little bit, and if you have some functor+flatten+wrap forming a monoid, we call that a monad." If you don't know what that stuff means then obviously it's not for you, but if you do, then it's actually a concise way to give an intuition for "what (or why) it is," which is basically just that flatten is associative and wrap is neutral.
Like if someone says they know about rings and modules, you might say that an ideal is just an R-submodule of R, which grants an interesting perspective and gives a quick, memorable definition. But if they don't know about modules, you might not give them that definition.
Re:Anecdotally, Programmers Dislike "Reduce"
by adastra22 on Thursday September 17, 2026 @11:21PM
(#49749864)
The point is that the terminology surrounding this is impenetrable and non intuitive. Rather than acknowledge that, we get a lesson on category theory, which is missing the point.
Re:Anecdotally, Programmers Dislike "Reduce"
by ndriscoll on Friday September 18, 2026 @12:01PM
(#49756337)
The person I replied to said they know what monoid means, so they're familiar with algebra. The explanation is intuitive for someone familiar with undergraduate algebra (adapted to also assume some familiarity with programming and show how it connects). That's literally where the meme comes from, an intuitive remark from an introductory text on category theory. If you think it's impenetrable, it's not for you. You don't have the correct background, so ignore it.
Re:Anecdotally, Programmers Dislike "Reduce"
by adastra22 on Friday September 18, 2026 @08:17PM
(#49761999)
It may have come from an old textbook, but its origin as a meme is a 2009 joke blog post that gets its humor from making fun of how obtuse and pedagogically inept that explanation is. When people reference it today, this is what they are referencing: https://james-iry.blogspot.com/2009/05/brief-incomplete-and-...
Re:Anecdotally, Programmers Dislike "Reduce"
by ndriscoll on Friday September 18, 2026 @09:52PM
(#49762550)
I'm aware of the meme, but it's not obtuse or pedagogically inept.
Like if someone says they're familiar with groups and Fourier transforms, but not what it means to say wavelets are the Fourier basis for the affine group, and I break that down, and you don't know what any of those words mean, that's not me giving an obtuse explanation of wavelets; that's you wandering into the wrong conversation.
Re:Anecdotally, Programmers Dislike "Reduce"
by antonvs on Thursday September 17, 2026 @02:41AM
(#49737193)
Do you want to understand monads, or do you want to understand the original quote that the joke you referenced was based on?
For the record, the original quote by Saunders Mac Lane is "a monad in X is just a monoid in the category of endofunctors of X, with product × replaced by composition of endofunctors and unit set by the identity endofunctor."
That quote is a statement in category theory. The author probably never heard of, say, Haskell - he was a pure mathematician. You can't usefully express that quote in Haskell code. You can treat it as a kind of formal description of what monads are, and Haskell generally conforms to that. But in that context, the quote itself is essentially using category theory as a metalanguage, in the same sort of way as one might write a mathematical statement that captures the semantics of some programming language expression.
That said, the quote can be handwavingly understood if you know what a monoid is, and that for monads, the identity object is the identity functor, its product is `join`[1] and its unit and multiplication satisfy the usual monoid laws.
For a concrete example, consider this Haskell expression using the `Maybe` monad:
do
x <- Just 3
return (x + 1)
That desugars to:
Just 3 >>= \x -> Just (x + 1)
Which we can desugar to an expression in terms of the monad's monoidal product, `join`, by substituting the definition of `>>=` in terms of `join`[1] to get:
join (fmap (\x -> Just (x + 1)) (Just 3))
You can evaluate that in Haskell and you'll get `Just 4`, just like the original expression.
So what happened there? The inner expression `fmap (\x -> Just (x + 1)) (Just 3)` applies the anonymous function to `Just 3` to get the double-wrapped `Just (Just 4)`. One of the `Just` wrappers is then eliminated with `join`.
(Btw, the fact that we have a Maybe within a Maybe here is related to the fact "monads are monoids in the category of endofunctors" - a category that maps to itself. That's where that part of the quote comes from.)
In this simple example, there's some unnecessary machinery - you can get the same result with `fmap (\x -> x + 1) (Just 3)`, without the extra `Just` wrapper or the `join` to eliminate it. But then you lose the ability to do things "in the monad": the anonymous function becomes just an ordinary function, it doesn't have access to the monadic wrapper. Many of the useful things that monads can do are because the wrapper is available in every function, so you can store state in it (Reader monad), create new wrapper instances with different state and pass those on (Writer and State monad), etc.
Re:Anecdotally, Programmers Dislike "Reduce"
by mcphage on Wednesday September 16, 2026 @03:42PM
(#49731898)
> Reduce also forces you to conjure up a "zero" value of the relevant type, which isn't usually difficult but it does constitute some extra mental overhead.
It's always worthwhile to consider what the result will be when you pass in an empty list.
Re:Anecdotally, Programmers Dislike "Reduce"
by Lvl999Noob on Thursday September 17, 2026 @12:29AM
(#49736427)
If you have need of a reducing operation though, you will still need to think about that value. If you are summing up a list of numbers, it doesn't matter whether you use reduce or a loop, you need to set some initial value.
Re:Anecdotally, Programmers Dislike "Reduce"
by mcphage on Thursday September 17, 2026 @10:38AM
(#49741482)
I agree, although most of the time you do end up needing to consider it with Map/Filter. It's just, it doesn't force you to. Which means either you think about it later, or it bites you in the ass because you didn't consider the empty case. Not always—and for those cases where you don't end up needing the empty case, Reduce is probably not necessary, and Map/Filter is sufficient.
Re:Anecdotally, Programmers Dislike "Reduce"
by globular-toast on Thursday September 17, 2026 @03:04AM
(#49737375)
> Reduce also forces you to conjure up a "zero" value of the relevant type
It's more accurately an identity. If you are multiplying the identity is 1. While I think most people are comfortable saying the sum of no elements is 0 it's perhaps less intuitive that the product of no elements is 1. This makes me think reduce might be preferred by those with a mathematical background.
Re:Anecdotally, Programmers Dislike "Reduce"
by chubot on Wednesday September 16, 2026 @02:28PM
(#49731007)
Related to the point about worse performance, I'm pretty sure I was there when reduce was "banished" from Python 3 -- demoted to functools.reduce(), instead of the builtin reduce() in Python 2
The story is that sometime in 2006 or 2007, Guido van Rossum was debugging why a web page in Google's internal code review tool (which he wrote) was taking 30+ seconds to render.
This is basically a "production" incident, since thousands of Google engineers relied on the tool. Requests like this were probably tying up threads and exhausting thread pools, perhaps
Eventually it was tracked down to a line wrapping algorithm written with reduce(). I don't think he wrote it -- it may have come in through a dependency. As many know, reduce() is basically:
s1 + s2
s1 + s2 + s3
s1 + s2 + s3 + s4
...
And that's O(n^2) when s_i are strings. And I think it showed up if you viewed a 5000+ line diff, or a 5000+ line file. (Newer programs like Github also suffer here)
I believe, in Python at that time, += was already optimized to avoid this (just like essentially all JS VMs are). Or you can use the idiom of append() to list and join() after.
But reduce() basically forces the inefficient implementation, and I'm sure this is still true in Python 3.
---
So basically Guido spent a long time debugging a performance problem related to reduce(), and made the decision to eject it, to help users avoid "footguns". I was his officemate at the time, so I recall this, but I wasn't involved directly
Also, somebody contributed reduce() to Python way back in the 90's, as well as other functional idioms. He wouldn't have added that himself -- it was never his preferred style.
He preferred a more imperative style. But he allowed those contributions, and then slightly regretted it later.
Re:Anecdotally, Programmers Dislike "Reduce"
by taeric on Wednesday September 16, 2026 @02:40PM
(#49731166)
This always feels odd to me. It would seem a fairly straight forward optimization of the interpreter to special case the different types that it can reduce.
That is, why couldn't they have done the essentially same trick that you reference for += with reduce?
Re:Anecdotally, Programmers Dislike "Reduce"
by justonceokay on Wednesday September 16, 2026 @02:50PM
(#49731290)
That sounds nice but in practice it’s probably not super helpful. Yes you could make a special case for reducing “+” over integers. But in python you can generally not promise that all the inputs are strictly integers, and you can’t even promise that your “+” function has no side effects.
Re:Anecdotally, Programmers Dislike "Reduce"
by bmandale on Wednesday September 16, 2026 @02:50PM
(#49731297)
There is no such trick. Python is only now getting those sort of JIT style optimizations, and that one in particular still hasn't hit. Do not use += on strings in a loop unless you are certain the iteration count will be small.
There is an optimization for lists, and maybe that's what GP is remembering. l += is functionally different from l = l +. The former mutates l, whereas the latter creates a new l. The difference matters when the line above is m = l. The mutation version will mutate m as well (they're the same reference), the creates new version will not. This optimization can just as easily turn into a footgun if the programmer is unaware of it, and in that sense is unpythonic.
Re:Anecdotally, Programmers Dislike "Reduce"
by kenferry on Wednesday September 16, 2026 @07:25PM
(#49734412)
I’m not a fan of this kind of “fancy” optimization anyway.
It’s too fragile. I may make some innocuous change, now the compiler cannot recognize the pattern and performance falls off the cliff.
I’d rather have the reliabile performance than the absolute fastest possible result. Then if there’s an issue I can catch and fix it reliably with profiling, not deal with a heisenbug based on whether the compiler can match the pattern.
Re:Anecdotally, Programmers Dislike "Reduce"
by bjourne on Wednesday September 16, 2026 @02:57PM
(#49731384)
Maybe you are misremembering the story? += deferred concatenation requires lazy strings and that didn't come until 10-15 years later. However, concatenating string lists with sum() was a common Python idiom at the time and it indeed incurred O(n^2) complexity. Gvr's reduce dislike was more about its syntax. It doesn't mesh well with Python's lambda syntax.
Re:Anecdotally, Programmers Dislike "Reduce"
by Maxatar on Wednesday September 16, 2026 @07:02PM
(#49734220)
> += deferred concatenation requires lazy strings and that didn't come until 10-15 years later.
CPython's += does not perform deferred concatenation and CPython does not use lazy strings. The optimization uses an eager in-place realloc if the string's ref-count is 1. This remains the optimization used even to this day and was introduced in 2005:
>However, concatenating string lists with sum() was a common Python idiom at the time
It could not possibly have been a common Python idiom since sum() explicitly rejected strings by throwing a TypeError. This was explicitly special cased to avoid the degenerate performance and the TypeError even has an error message saying "TypeError: sum() can't sum strings [use ''.join(seq) instead]".
>Gvr's reduce dislike was more about its syntax. It doesn't mesh well with Python's lambda syntax.
No it had nothing to do with mixing with lambda syntax, on the contrary GvR actually wanted to remove reduce and lambda (and map and filter as well). Here is the actual article by GvR regarding removing reduce, absolutely nothing in it involves how it mixes with lambda expressions.
>So now reduce(). This is actually the one I've always hated most, because, apart from a few examples involving + or *, almost every time I see a reduce() call with a non-trivial function argument, I need to grab pen and paper to diagram what's actually being fed into that function before I understand what the reduce() is supposed to do. So in my mind, the applicability of reduce() is pretty much limited to associative operators, and in all other cases it's better to write out the accumulation loop explicitly.
Re:Anecdotally, Programmers Dislike "Reduce"
by chubot on Wednesday September 16, 2026 @08:52PM
(#49735107)
Yes thanks for finding the Python 2.4 release page which shows the optimization! So I remembered correctly -- Python already had that optimization back then. (There seems to be a large amount of confusion on that in this subthread)
And the March 2005 Artima post is also a very good reference! That actually predates my story, since Guido hadn't joined Google by then. I recall that he joined in December 2005.
So maybe the bug I remember was more of a "push" in the direction he had already thought of, not the direct inspiration.
It's clear from the blog post that he disliked all of map / filter / reduce, and then I'm sure that users or python-dev pushed back on removing them, so he settled for banishing reduce() to the stdlib.
Re:Anecdotally, Programmers Dislike "Reduce"
by chubot on Wednesday September 16, 2026 @08:47PM
(#49735073)
Python has always had a bytecode compiler that did some minor optimizations, since its inception in the 90's. The issue is that optimizations are very hard to do correctly in the compiler because Python is so dynamic. Any piece of code could suddenly redefine mytype.__add__() and so forth.
Re:Anecdotally, Programmers Dislike "Reduce"
by Zak on Wednesday September 16, 2026 @06:06PM
(#49733686)
I find that decision a bit odd given that accumulating a string with a loop is also quadratic in Python if you use = instead of +=, or even if you use += when the left operand isn't provably unshared. I don't believe removing loops was seriously considered.
The footgun isn't `reduce` in particular, but failing to use `join`.
Re:Anecdotally, Programmers Dislike "Reduce"
by edflsafoiewq on Wednesday September 16, 2026 @07:44PM
(#49734582)
Doesn't reduce force the accumulator to be shared though? Both the reduce and the lambda are holding onto references to acc, which defeats any "single reference" optimizations.
Re:Anecdotally, Programmers Dislike "Reduce"
by Zak on Wednesday September 16, 2026 @08:18PM
(#49734847)
It might - let's assume it does. My point is that it's better to use the explicit optimized method for joining strings in a performance-sensitive context than to try to meet the conditions for an implicit optimization.
Re:Anecdotally, Programmers Dislike "Reduce"
by ndriscoll on Wednesday September 16, 2026 @10:04PM
(#49735590)
Why is it still active? Even an interpreter with no lookahead could see that it goes out of scope immediately when f returns (it gets shadowed on that line), so as long as there's no guarantee about when finalizers get called, it should be able to mark it dead inside of reduce as soon as it's passed to f. Like move semantics here should be a general pattern for optimization, no?
Re:Anecdotally, Programmers Dislike "Reduce"
by ndriscoll on Wednesday September 16, 2026 @10:21PM
(#49735699)
Fair, I suppose there's no end to the level of insanity that a programmer can do in a dynamic language. I'd think it could perhaps still look to see there's no catch, but maybe eval makes even that impossible.
Re:Anecdotally, Programmers Dislike "Reduce"
by edflsafoiewq on Wednesday September 16, 2026 @11:46PM
(#49736152)
If the s are small the usual geometric buffer growth mitigates that. Of course you can compute the final buffer size in this case, but often you have a bunch of dynamically-generated strings of different sizes.
Re:Anecdotally, Programmers Dislike "Reduce"
by OJFord on Thursday September 17, 2026 @03:47AM
(#49737678)
I suppose `reduce` as built-in is the footgun because it's too easy to reach for. Now if someone doesn't know about `join` perhaps they look up how to do it because they think 'surely there's a better way than a loop without an import'.
Re:Anecdotally, Programmers Dislike "Reduce"
by z500 on Thursday September 17, 2026 @12:15PM
(#49742953)
> Also, somebody contributed reduce() to Python way back in the 90's, as well as other functional idioms. He wouldn't have added that himself -- it was never his preferred style.
> He preferred a more imperative style. But he allowed those contributions, and then slightly regretted it later.
That would explain why they're so inconvenient to chain.
Re:Anecdotally, Programmers Dislike "Reduce"
by sumolessons on Wednesday September 16, 2026 @02:39PM
(#49731160)
I wanted to add that from personal experience tastes can change! I didn't like reduce when I was first exposed to functional programming, but have come to prefer it.
Might be nonsensical, but one thing I sometimes wonder is why I reach for reducing a list to a value more often than I need to generate a list from a starting value. I guess the asymmetry has something to do with the kinds of applications I work on.
Re:Anecdotally, Programmers Dislike "Reduce"
by norir on Wednesday September 16, 2026 @02:42PM
(#49731192)
Anywhere that I could use reduce, I instead write a tail recursive function. This is also why I do not and will not ever choose python or javascript voluntarily.
Re:Anecdotally, Programmers Dislike "Reduce"
by juancn on Wednesday September 16, 2026 @02:43PM
(#49731211)
I like it conceptually, but the main issue for me with reduce is that it's hard to know exactly how the reduction will actually be executed.
The FUBAR potential with map and filter is much smaller, with reduce it depends on deep knowledge of the internals of the reduction itself, which makes it not as useful as a safe abstraction.
Re:Anecdotally, Programmers Dislike "Reduce"
by Glyptodon on Wednesday September 16, 2026 @02:51PM
(#49731311)
It's on my list of things that are awkwardly named because there's not a great name to choose, particularly given how wide the different use cases are.
Re:Anecdotally, Programmers Dislike "Reduce"
by franey on Wednesday September 16, 2026 @02:51PM
(#49731314)
At least in TypeScript, it's a bit clunky to type, and I usually forget the order of the reduce function's arguments (accumulator, current item). Maybe it's just me, but it's especially easy to forget the order when the position of the accumulator is the 1st argument to the callback but the 2nd argument of the reduce function:
In .filter(), The current item is the 1st argument and the intermediate/accumulated value comes later: filter((currentItem, index, intermediateArray)) => ...)
I use .filter() more often, so that argument ordering where currentItem is right next to the array is more intuitive for me
Re:Anecdotally, Programmers Dislike "Reduce"
by yojo on Wednesday September 16, 2026 @03:15PM
(#49731580)
In TS/JS you’re usually inlining the reducer fn, and there’s something hard to read/especially ugly about the comma after the bracket or arrow fn into the initalValue.
That said, when I’m reducing a list, I still use reduce.
Re:Anecdotally, Programmers Dislike "Reduce"
by franey on Wednesday September 16, 2026 @03:37PM
(#49731833)
That's an interesting idea. I might get hung up for cases where "previous" is a different type from "current", like if you're reducing a list of objects into a single object. You've got the current item of the array and the current state of the accumulator, so they're kind of both current. Or you've got the last state and current item, but "last" is ambiguous.
In general, I find that if something is hard to describe in plain language, it's hard to code. Reducers are a bit clunky to talk about, which could make them harder to reason about, too.
Re:Anecdotally, Programmers Dislike "Reduce"
by roblh on Thursday September 17, 2026 @12:39AM
(#49736481)
I think I've written this before and generally people are horrified, but a neat trick I like to do for a little bit of concurrency is making the first argument an async function.
That means you have to await the accumulator at some point before you return it, but anything you do before that call all gets fired off immediately. Then each invidivual iteration waits for the one before it to finish before finishing itself.
It's a pretty niche pattern, but it's a good way to make your coworkers do a double take while giving you quite a bit of control over exactly how it behaves. Similar to Promise.all, but more expressive I feel.
Re:Anecdotally, Programmers Dislike "Reduce"
by patwolf on Wednesday September 16, 2026 @03:16PM
(#49731590)
I've worked with developers that were reduce maximalist. During PR reviews, anything that could be rewritten with reduce was flagged. One of the benefits of AI is not having to care as much about things like that.
If you are multiplying, you are likely doing heavy math, and you'll be using numpy - which does not need reduce either.
If you are going to return a list of dict, then it's much faster to mutate the results, so using "reduce" will have significant performance implications (unless you want to return input argument, mis-using it as a glorified "for" loop)
And if returning not a list/dict, if you can use "min" or "max" or "any" or "all" or "next" (take the first element), then you should use it - it will be easier to read and faster too.
So what does this leave us for "reduce"? Frankly, not much. I've only seen it in merging immutable status codes, and that was pretty niche usecase to begin with.
(this was all for Python. In other languages without nice list of built-ins reduce might make more sense)
I come across reduce once in a few months, then I think it's a neat trick and a nice to have function.
then I forget it's even available and don't ever use unless these days LLM brings it up again.
Maybe there's just a better way to think about it and I'm still thinking about it way too much like a programmer
You "accumulate" an answer one item at a time, but there's no guarantee any dimensions are getting reduced.
You can easily duplicate the effects of map with reduce, for example, so the dims would stay the same. You could even expand dimensions, if you like, turning a 1-d array with n elements into an s X t 2-d array. If the reducing function tracks the total number of elements seen, it can easily know when to start a new row.
This is part of why people keep pointing out the name, "reduce", is a bit misleading.
`fold` is awesome and super useful. It's the easiest and most convenient way to turn a collection into a single value. Put me anecdotally in the opposite bucket.
You will eventually learn about something called "for loop", and it will be nice.
Indeed, this is why everyone knows the J programming language.
Plus it forces you out of whatever lazy/streaming paradigm you had going on. If your foldr produces a list, downstream can start consuming it in constant memory as long as you let it do its thing.
I.e. there is no initial value to pass in, but the result is an Optional to handle the empty iterator case. That’s how rust does it, for example:
https://doc.rust-lang.org/std/iter/trait.Iterator.html#metho...
A for loop gives you better memory management and speed, but the tradeoff only makes sense to me if you're doing embedded work or something. Otherwise, eat the .000000000001% speed loss to reduce the risk of logic errors, typos, etc. and to improve developer ergonomics.
Adding numbers like this is not common in real world code. Now let's say instead of adding x, you have too look up X in a cache with an additional "type" param and update a metric of cache hits (or misses). You have to define a free function to keep your fold readable and understandable. In for loop it's much easier to understand.
I agree. But you'd do that for a for-loop, too, unless you want a bloated for-loop.
> In for-loop it's much easier to understand.
I have to disagree there. You'd still be working with a free function, or you'd be working with a bloated for-loop body.
Combining cache loopkups, metric tracking, etc. runs into SOC issues that IME for-loops just let imperative developers get away with until it comes time to test their code.
Furthermore, free functions aren't bad. They're good. They're a self-documenting abstraction. Unless you name it `function_one` or something.
Having my fold lambda do its primary business role but call `update_cache_and_metrics` makes it unnecessary for someone reading the flow of logic from even needing to go read the body of that free function.
Associativity also makes fold hard. It's not super trivial to know when you might need e.g. left fold vs right fold
Suppose we have foldr as in [A] -> B -> ((A, B) -> B) -> B, foldl as in [A] -> B -> ((B, A) -> B) -> B, and reduce as in [A] -> ((A, A) -> A) -> A.
Then we have foldr list value operator = reduce [\b -> operator a b | a <- list] (.), foldl list value operator = foldr (reverse list) value (flip operator), and in the case of a finite non-empty list and associative operator, we have reduce list operator = foldr (tail list) (head list) operator = foldl (init list) (last list) operator.
So these are all basically slight re-parametrizations of each other.
The fact that you wrote this comment with Hindley-Milner-ish notation already makes your an outlier.
In languages like python or Java though, you don't really have access to many of the higher power functional traversals however. So that puts you into a similar kind of bind as working in a language with only while loops
Care to explain?
I feel like this is a case of personal preference over actual issue.
While/for can achieve the same thing, sometimes while is more practical as the steps to complete are unknown. But sure, stick your simple iterating a fixed collection as why it demonstrates while is a lesser language feature.
Usually, when the same code can be written either as range-based "for" or as a "while", the "for" will look better and have fewer possibility of bugs. If you have examples otherwise, I'd like to see them.
(Note I am specifically talking about range-based/iterator-based "for", not the C's variant. Nor am I talking the cases where the "for" is hard to use, like when the size might change at runtime)
You're not the OP, you/me we're only guessing what he/she might've meant.
That said, I personally don’t think it’s smelly at all.
Fold is a recursion scheme
More complicated recursion schemes are progressively harder to read. Probably not great if you are not doing code golf
If the algortihm doesn't work the same forward, backwards, and with a tree scan, it ain't reduce (as a first approximation not IFF)
[1]Or if they have, their only encounter with it is the "a monad is just a monoid in the category of endofunctors" meme.
So basically wrapping and flattening behave in a sane way. Flatten is your multiply, wrap is your multiplicative identity, and it's like a monoid if you squint.
Like if someone says they know about rings and modules, you might say that an ideal is just an R-submodule of R, which grants an interesting perspective and gives a quick, memorable definition. But if they don't know about modules, you might not give them that definition.
Like if someone says they're familiar with groups and Fourier transforms, but not what it means to say wavelets are the Fourier basis for the affine group, and I break that down, and you don't know what any of those words mean, that's not me giving an obtuse explanation of wavelets; that's you wandering into the wrong conversation.
For the record, the original quote by Saunders Mac Lane is "a monad in X is just a monoid in the category of endofunctors of X, with product × replaced by composition of endofunctors and unit set by the identity endofunctor."
That quote is a statement in category theory. The author probably never heard of, say, Haskell - he was a pure mathematician. You can't usefully express that quote in Haskell code. You can treat it as a kind of formal description of what monads are, and Haskell generally conforms to that. But in that context, the quote itself is essentially using category theory as a metalanguage, in the same sort of way as one might write a mathematical statement that captures the semantics of some programming language expression.
That said, the quote can be handwavingly understood if you know what a monoid is, and that for monads, the identity object is the identity functor, its product is `join`[1] and its unit and multiplication satisfy the usual monoid laws.
For a concrete example, consider this Haskell expression using the `Maybe` monad:
That desugars to: Which we can desugar to an expression in terms of the monad's monoidal product, `join`, by substituting the definition of `>>=` in terms of `join`[1] to get: You can evaluate that in Haskell and you'll get `Just 4`, just like the original expression.So what happened there? The inner expression `fmap (\x -> Just (x + 1)) (Just 3)` applies the anonymous function to `Just 3` to get the double-wrapped `Just (Just 4)`. One of the `Just` wrappers is then eliminated with `join`.
(Btw, the fact that we have a Maybe within a Maybe here is related to the fact "monads are monoids in the category of endofunctors" - a category that maps to itself. That's where that part of the quote comes from.)
In this simple example, there's some unnecessary machinery - you can get the same result with `fmap (\x -> x + 1) (Just 3)`, without the extra `Just` wrapper or the `join` to eliminate it. But then you lose the ability to do things "in the monad": the anonymous function becomes just an ordinary function, it doesn't have access to the monadic wrapper. Many of the useful things that monads can do are because the wrapper is available in every function, so you can store state in it (Reader monad), create new wrapper instances with different state and pass those on (Writer and State monad), etc.
---
[1] x >>= f = join (fmap f x)
It's always worthwhile to consider what the result will be when you pass in an empty list.
It's more accurately an identity. If you are multiplying the identity is 1. While I think most people are comfortable saying the sum of no elements is 0 it's perhaps less intuitive that the product of no elements is 1. This makes me think reduce might be preferred by those with a mathematical background.
The story is that sometime in 2006 or 2007, Guido van Rossum was debugging why a web page in Google's internal code review tool (which he wrote) was taking 30+ seconds to render.
This is basically a "production" incident, since thousands of Google engineers relied on the tool. Requests like this were probably tying up threads and exhausting thread pools, perhaps
Eventually it was tracked down to a line wrapping algorithm written with reduce(). I don't think he wrote it -- it may have come in through a dependency. As many know, reduce() is basically:
And that's O(n^2) when s_i are strings. And I think it showed up if you viewed a 5000+ line diff, or a 5000+ line file. (Newer programs like Github also suffer here)I believe, in Python at that time, += was already optimized to avoid this (just like essentially all JS VMs are). Or you can use the idiom of append() to list and join() after.
But reduce() basically forces the inefficient implementation, and I'm sure this is still true in Python 3.
---
So basically Guido spent a long time debugging a performance problem related to reduce(), and made the decision to eject it, to help users avoid "footguns". I was his officemate at the time, so I recall this, but I wasn't involved directly
Also, somebody contributed reduce() to Python way back in the 90's, as well as other functional idioms. He wouldn't have added that himself -- it was never his preferred style.
He preferred a more imperative style. But he allowed those contributions, and then slightly regretted it later.
https://docs.python.org/3/library/functools.html#functools.r...
That is, why couldn't they have done the essentially same trick that you reference for += with reduce?
There is an optimization for lists, and maybe that's what GP is remembering. l += is functionally different from l = l +. The former mutates l, whereas the latter creates a new l. The difference matters when the line above is m = l. The mutation version will mutate m as well (they're the same reference), the creates new version will not. This optimization can just as easily turn into a footgun if the programmer is unaware of it, and in that sense is unpythonic.
s = s + "foo"
and
s += "foo"
Since 2005 with the release of Python 2.4:
https://docs.python.org/3/whatsnew/2.4.html#optimizations
It’s too fragile. I may make some innocuous change, now the compiler cannot recognize the pattern and performance falls off the cliff.
I’d rather have the reliabile performance than the absolute fastest possible result. Then if there’s an issue I can catch and fix it reliably with profiling, not deal with a heisenbug based on whether the compiler can match the pattern.
CPython's += does not perform deferred concatenation and CPython does not use lazy strings. The optimization uses an eager in-place realloc if the string's ref-count is 1. This remains the optimization used even to this day and was introduced in 2005:
https://docs.python.org/3/whatsnew/2.4.html#optimizations
>However, concatenating string lists with sum() was a common Python idiom at the time
It could not possibly have been a common Python idiom since sum() explicitly rejected strings by throwing a TypeError. This was explicitly special cased to avoid the degenerate performance and the TypeError even has an error message saying "TypeError: sum() can't sum strings [use ''.join(seq) instead]".
>Gvr's reduce dislike was more about its syntax. It doesn't mesh well with Python's lambda syntax.
No it had nothing to do with mixing with lambda syntax, on the contrary GvR actually wanted to remove reduce and lambda (and map and filter as well). Here is the actual article by GvR regarding removing reduce, absolutely nothing in it involves how it mixes with lambda expressions.
https://www.artima.com/weblogs/viewpost.jsp?thread=98196
>So now reduce(). This is actually the one I've always hated most, because, apart from a few examples involving + or *, almost every time I see a reduce() call with a non-trivial function argument, I need to grab pen and paper to diagram what's actually being fed into that function before I understand what the reduce() is supposed to do. So in my mind, the applicability of reduce() is pretty much limited to associative operators, and in all other cases it's better to write out the accumulation loop explicitly.
And the March 2005 Artima post is also a very good reference! That actually predates my story, since Guido hadn't joined Google by then. I recall that he joined in December 2005.
So maybe the bug I remember was more of a "push" in the direction he had already thought of, not the direct inspiration.
It's clear from the blog post that he disliked all of map / filter / reduce, and then I'm sure that users or python-dev pushed back on removing them, so he settled for banishing reduce() to the stdlib.
https://github.com/python/cpython/commit/a70b19147fd163744be...
The footgun isn't `reduce` in particular, but failing to use `join`.
-map: [x*2 for x in xs]
-filter: [x for x in xs if x%0==2]
-reduce: ummm..
Maybe something like:
sum = x+ret for x in xs from ret=0
> He preferred a more imperative style. But he allowed those contributions, and then slightly regretted it later.
That would explain why they're so inconvenient to chain.
I find the two ways that you call it to be a bit annoying (not a showstopper). It just seems a bit "kludgy" to me.
Might be nonsensical, but one thing I sometimes wonder is why I reach for reducing a list to a value more often than I need to generate a list from a starting value. I guess the asymmetry has something to do with the kinds of applications I work on.
The FUBAR potential with map and filter is much smaller, with reduce it depends on deep knowledge of the internals of the reduction itself, which makes it not as useful as a safe abstraction.
I use .filter() more often, so that argument ordering where currentItem is right next to the array is more intuitive for me
That said, when I’m reducing a list, I still use reduce.
I think if `reduce` looked more functional or more like Erlang code, it'd be easier to read and digest.
I had similar trouble, but I know call the "accumulator" just "previous" which makes it more logical in my head:
.reduce( (previous, current) => previous+current, 0 );
In general, I find that if something is hard to describe in plain language, it's hard to code. Reducers are a bit clunky to talk about, which could make them harder to reason about, too.
allTasks.reduce((acc, item) => { acc[item.label] = t => t.item.label === item.label; return acc; }, {} as Record<string, (t: typeof tasks[number]) => boolean>)
tasks.reduce<Record<string, (t: typeof tasks[number]) => boolean>>((acc, item) => ..., {})
Also imo it's cleaner to reduce to an object with something like this as the callback:
(acc, item) => ({ ...acc, [item.label]: t => t.label === item.label })
That means you have to await the accumulator at some point before you return it, but anything you do before that call all gets fired off immediately. Then each invidivual iteration waits for the one before it to finish before finishing itself.
It's a pretty niche pattern, but it's a good way to make your coworkers do a double take while giving you quite a bit of control over exactly how it behaves. Similar to Promise.all, but more expressive I feel.