49 points vinhnx 2 days ago 93 comments
Skeime 2 days ago | parent
With `reduce`, the result could be anything, and in an imperative language, side effects are also possible. So it's just a loop with worse syntax.
(Admittedly, in an imperative language, `map` and `filter` could also have side effects, though I think most people would consider this bad style.)
Hackbraten 2 days ago | parent
I think I’ve seen several language core APIs have this in their contract, e.g. `Stream#reduce` in Java [0] (emphasis mine):
> accumulator - an *associative, non-interfering, stateless* function for combining two values
[0]: https://docs.oracle.com/javase/8/docs/api/java/util/stream/S...
Someone 2 days ago | parent
Skeime 2 days ago | parent
(Note that by "loop over a collection", I explicitly mean a looping construct that gives the elements of the collection directly, instead of looping over indices and extracting the elements manually.)
speedstyle 2 days ago | parent
.fold(init, move |acc, x| {…}) // or
.fold((state, init), |(state, acc), x| {…}).1Someone 2 days ago | parent
- a slightly awkward one that takes a partial result and the next value to produce a new partial result
- one that maps the final partial result to the result
Also, in many languages, when reading the code, you have to skip initialization of the partial result, read the lambda, and then jump back to make sense of the initial values
I think something like awk’s syntax, with BEGIN and END blocks would improve on that. Example of a first go at such syntax (needs work):
Items.BEGIN
min = ∞
max = -∞
sum = 0
n = 0
ITER
min = Min(min,_)
max = Max(max,_)
n += 1
sum += _
RETURN
average = sum / n
(min, max, average)
Advantages:- items in the partial results have names, making them easier to understand
- result also is easier to understand
Price paid is wordiness, and you cannot simply write a function name for either of the lambdas.
However, I think the latter only is useful in case the partial result is the final result. There, you can keep
sum = items.reduce(0,+)
if you want to.futune 2 days ago | parent
Here's a hypothesis: The fact that the same operation has half a dozen different names makes it sound like there is a lot to learn. If I am totally familiar with fold, and i come upon a reduce, I may need to think more about what's going on, which is distracting.
I don't think map and filter have so many synonyms? I know select for filter, but it seems to me less common.
3836293648 2 days ago | parent
hyperhello 2 days ago | parent
el_oni 2 days ago | parent
But for unioning a bunch of spark dataframes together i think
df = reduce(DataFrame.union, list_of_dfs)
is much nicer than df, *rest = list_of_dfs
for other in rest:
df = df.union(other)
People just get a bit funny, especially now you have to import it from functoolseimrine 2 days ago | parent
theamk 2 days ago | parent
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)
rsfern 1 day ago | parent
Pinus 1 day ago | parent
theamk 18 hours ago | parent
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!olivewong 2 days ago | parent
evnix 2 days ago | parent
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.
bjourne 3 hours ago | parent
sigbottle 2 hours ago | parent
Maybe there's just a better way to think about it and I'm still thinking about it way too much like a programmer
karmakaze 2 days ago | parent
billyp-rva 2 days ago | parent
slopnt 2 days ago | parent
ducaale 2 days ago | parent
- arg1, arg2 and return value are all of the same type e.g `ADD`, `MAX`, `CONCAT` etc
- and there is an identity value e.g zero for `ADD`, -math.inf for `MAX`
I recommend checking this article[1] on how monoids play nicely with reduce.
[1] https://fsharpforfunandprofit.com/posts/monoids-without-tear...
g8oz 2 days ago | parent
japgolly 1 day ago | parent
`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.
wannabe44 3 hours ago | parent
You will eventually learn about something called "for loop", and it will be nice.
t-3 3 hours ago | parent
robrenaud 2 hours ago | parent
Indeed, this is why everyone knows the J programming language.
mrkeen 3 hours ago | parent
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.
8note 3 hours ago | parent
mrkeen 3 hours ago | parent
bspammer 2 hours ago | parent
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...
nightpool 3 hours ago | parent
Associativity also makes fold hard. It's not super trivial to know when you might need e.g. left fold vs right fold
Chinjut 3 hours ago | parent
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.
kaoD 5 minutes ago | parent
The fact that you wrote this comment with Hindley-Milner-ish notation already makes your an outlier.
rspeele 1 day ago | parent
Reduce has an accumulator and a 2-arg function and languages are not very consistent amongst each other as to whether it's reduce(initial_acc, callback(acc, elem)) or reduce(callback(acc, elem), initial_acc) or reduce(callback(elem, acc), initial_acc) or what.
Hard to remember. Also some languages have a version of reduce that doesn't take an initial accumulator at all, which is just a footgun waiting for you to hit an empty collection. Also ALSO, the accumulator can easily become awkward in languages that don't support anonymous types or don't support easy mutation of an anonymous type record. Which is most of them!
jiehong 28 minutes ago | parent
While map is a great name, I always struggle to remember if ‘filter’ keeps elements that match the condition or removes them.
I mean, it’s like a colander: you filter noodles and water, but which one do you keep? The noodles, right? But, replace noodles with tea and now you want to keep the water part.
Naming is hard I guess.
zelphirkalt 5 minutes ago | parent
s-zeng 1 day ago | parent
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
grebc 2 hours ago | parent
Care to explain?
polonbike 2 hours ago | parent
theamk 2 hours ago | parent
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)grebc 1 hour ago | parent
I feel like this is a case of personal preference over actual issue.
snackbroken 1 day ago | parent
mrkeen 3 hours ago | parent
snackbroken 3 hours ago | parent
sigbottle 3 hours ago | parent
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)
snackbroken 1 hour ago | parent
[1]Or if they have, their only encounter with it is the "a monad is just a monoid in the category of endofunctors" meme.
sigbottle 1 hour ago | parent
mcphage 2 hours ago | parent
It's always worthwhile to consider what the result will be when you pass in an empty list.
snackbroken 1 hour ago | parent
chubot 3 hours ago | parent
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.
https://docs.python.org/3/library/functools.html#functools.r...
taeric 3 hours ago | parent
That is, why couldn't they have done the essentially same trick that you reference for += with reduce?
justonceokay 3 hours ago | parent
bmandale 3 hours ago | parent
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.
bjourne 3 hours ago | parent
hn_go_brrrrr 3 hours ago | parent
Zak 6 minutes ago | parent
The footgun isn't `reduce` in particular, but failing to use `join`.
semiinfinitely 3 hours ago | parent
ChrisMarshallNY 3 hours ago | parent
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.
sumolessons 3 hours ago | parent
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.
norir 3 hours ago | parent
juancn 3 hours ago | parent
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.
WesolyKubeczek 3 hours ago | parent
Glyptodon 3 hours ago | parent
franey 3 hours ago | parent
array.reduce(
(accumulator, currentItem) => {...},
initialValue,
)
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
chrisandchris 3 hours ago | parent
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 );
franey 2 hours ago | parent
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.
BariumBlue 3 hours ago | parent
I think if `reduce` looked more functional or more like Erlang code, it'd be easier to read and digest.
yojo 2 hours ago | parent
That said, when I’m reducing a list, I still use reduce.
patwolf 2 hours ago | parent
mcphage 2 hours ago | parent
adverbly 52 minutes ago | parent
The problem with reduce is that it can do so much, and therefore it is less clear when reading it quickly what it might be doing.
hungryhobbit 50 minutes ago | parent
Reduces are used much, much less often. Most devs don't get familiar with them as a result, so every time they have to read a `reduce` they have to re-learn it. And of course, it's a much more involved/complex function, so that exacerbates it.
scelerat 38 minutes ago | parent
So I love reduce, and have for many years.
jakub_g 27 minutes ago | parent
Sometimes people abuse .map as well to do things that are not obvious (i.e. instead of mapping elements of an array to another array, they modify global variables in a for-loop fashion, and discard the result).
But reduce is abused more often and you always need to think really hard if e.g. the initial accumulator is passed or not (it's optional in some languages!), if a correct one is passed (when a compound type is used) and so on.
bunderbunder 27 minutes ago | parent
But I think the real reason might be even simpler: you can't tell what it does just from the name. What `map` does is consistent with well-known programming jargon. What `filter` does is consistent with the word's everyday meaning. But if you don't already know what `reduce` does, it's name isn't even enough to hazard an educated guess.
That's not true in Clojure because for lisp programmers for two reasons. First, `reduce` is a ubiquitous and well-known concept in lisp.
Second, in most lisps manually doing the same task with imperative code is an ugly verbose eyesore. But in algol-style languages, the imperative alternative is only 1-2 extra lines of very simple code, so using `reduce` is arguably just code golf.
skybrian 27 minutes ago | parent
If there's no standard function for it, it's trivial to write a utility function.
And as part of writing the function, give it a good name and think a bit about the order of operations?
So I think reduce() is just unnecessarily generic, unless it's part of a more complicated system like running a map-reduce.
dochne 16 minutes ago | parent
While not as functionally pure, I always appreciate the Ruby each_with_object https://ruby-doc.org/3.4.1/Enumerable.html#method-i-each_wit... as a more pleasant interface for it.
agentultra 16 minutes ago | parent
“We can’t have map in our codebase, we need to be able to hire anyone off the street and have them comfortable in our codebase.”
Well… since when did we hire random people off the street?
I’m used to functional programming. For me, reduce is perfectly normal. Fewer intermediate variables. No pesky statements, just a nice expression. Great.
Buuuut… some languages think implementing tail call optimization is too hard or bad or for ivory tower academics. Or they’re dynamically typed. And then reduce does become difficult to special case and make performant. So even if you like the juice it’s probably not worth the squeeze.
It was a great time working with Haskell professionally. I didn’t have to constantly defend my style of programming! But in “everything” languages… well you do. Everyone has to agree on which subset to use. And programmers are like cats. Good luck getting them to agree on anything. Even once you agree there will always be that one challenging the decree.
yipinwong 16 minutes ago | parent
I use both, but do not like reduce at all. It's harder to read, yes. But I see the point of using them all.
the_other 5 minutes ago | parent
I really like taking the implementation away from the call site, so that the call site reads
const myNewValue = data.reduce(doSomethingMagic);
(and then `doSomethingMagic` is defined somewhere else). So simple.I failed a job interview once by using reduce() in a coding test. The reviewer didn't understand why I hadn't used a loop. Loops are easier, for sure, but they sprawl and are open to hacking. They can bring in state from outside the loop. They make the call site long (you always have to read the implementation to learn that you don't need to read it). The same interviewer actively liked to have loop bodies modify the loop conditions (e.g. by taking items out of the source array and decrementing the end condition, so the loop would end earlier). That's the kind of "clever" I find unpredictable and hard to think about. Probably a good thing he rejected me.
anyfoo 4 minutes ago | parent
Honestly, sounds like the reviewer failed the interview, not the other way around.
baxuz 2 minutes ago | parent
catapart 3 minutes ago | parent
I realize it's not the most efficient way to work, but I like my code to read like instructions. There's nothing reduce will do that a for loop won't accomplish and the for loop (+ an accumulator, of course) is more clearly "readable" than reduce. If I read map, I know what's going on. If I read filter, I know what's going on. If I read reduce, I have to figure out what's going on, even if I'm pretty sure what is going on. If I could rely on reduce to always give me back an element of the input array, I would use it more. But since it can give back anything, I prefer the simplicity of a for loop.
[0] I don't have any suggestions for "better" names because the whole operation is hard to sum up in a word? "dispatch" makes sense, as a function dispatching a function over each element in an array, but it masks the concept of accumulation from return values. "transform" is accurate, but hardly descriptive at all. the list goes on. It's an undeniably useful little function, it's just hard to make it easy to understand and therefore debug.
scotty79 1 minute ago | parent
combine, accumulate it aggregate would have way more use.