Anecdotally, Programmers Dislike "Reduce"
Thread
Loading the complete thread in the background. This saved snapshot is available now. Refresh
Unofficial Hacker News client; not affiliated with Y Combinator.
Anecdotally, Programmers Dislike "Reduce"
Loading the complete thread in the background. This saved snapshot is available now. Refresh
Unofficial Hacker News client; not affiliated with Y Combinator.
eimrine · · focus · HN ↗
theamk · · focus · HN ↗
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 · · focus · HN ↗
Pinus · · focus · HN ↗
theamk · · focus · HN ↗
mahboi · · focus · HN ↗
evnix · · focus · HN ↗
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 · · focus · HN ↗
sigbottle · · focus · HN ↗
Maybe there's just a better way to think about it and I'm still thinking about it way too much like a programmer
bjourne · · focus · HN ↗
sigbottle · · focus · HN ↗
KingMob · · focus · HN ↗
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.
karmakaze · · focus · HN ↗
billyp-rva · · focus · HN ↗
slopnt · · focus · HN ↗
g8oz · · focus · HN ↗
japgolly · · focus · HN ↗
`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 · · focus · HN ↗
You will eventually learn about something called "for loop", and it will be nice.
t-3 · · focus · HN ↗
robrenaud · · focus · HN ↗
Indeed, this is why everyone knows the J programming language.
mrkeen · · focus · HN ↗
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 · · focus · HN ↗
mrkeen · · focus · HN ↗
bspammer · · focus · HN ↗
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:
<a href="https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.reduce" rel="nofollow">https://doc.rust-lang.org/std/iter/trait.Iterator.html#metho...
KPGv2 · · focus · HN ↗
bspammer · · focus · HN ↗
KPGv2 · · focus · HN ↗
wannabe44 · · focus · HN ↗
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. You have to define a free function to keep your fold readable and understandable. In for loop it's much easier to understand.
KPGv2 · · focus · HN ↗
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.
nightpool · · focus · HN ↗
Associativity also makes fold hard. It's not super trivial to know when you might need e.g. left fold vs right fold
Chinjut · · focus · HN ↗
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 just slight re-parametrizations of each other.
kaoD · · focus · HN ↗
ChrisMarshallNY · · focus · HN ↗
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 · · focus · HN ↗
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 · · focus · HN ↗
juancn · · focus · HN ↗
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.
yakshaving_jgt · · focus · HN ↗
WesolyKubeczek · · focus · HN ↗
Glyptodon · · focus · HN ↗
franey · · focus · HN ↗
I use .filter() more often, so that argument ordering where currentItem is right next to the array is more intuitive for me
chrisandchris · · focus · HN ↗
I had similar trouble, but I know call the "accumulator" just "previous" which makes it more logical in my head:
.reduce( (previous, current) => p+c, 0 );
franey · · focus · HN ↗
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 · · focus · HN ↗
I think if `reduce` looked more functional or more like Erlang code, it'd be easier to read and digest.
yojo · · focus · HN ↗
That said, when I’m reducing a list, I still use reduce.
flufluflufluffy · · focus · HN ↗
allTasks.reduce((acc, item) => { acc[item.label] = t => t.item.label === item.label; return acc; }, {} as Record<string, (t: typeof tasks[number]) => boolean>)
LittleLily · · focus · HN ↗
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 })
flufluflufluffy · · focus · HN ↗
roblh · · focus · HN ↗
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.
patwolf · · focus · HN ↗
dang · · focus · HN ↗