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.
Skeime · · focus · HN ↗
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 · · focus · HN ↗
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]: <a href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#reduce-T-java.util.function.BinaryOperator-" rel="nofollow">https://docs.oracle.com/javase/8/docs/api/java/util/stream/S...
Someone · · focus · HN ↗
Skeime · · focus · HN ↗
(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 · · focus · HN ↗
KPGv2 · · focus · HN ↗
Does the written contract for for-loops specify this as well? Because that's the obvious alternative for a reduce that an imperative programmer would reach for: a for-loop with the programmer managing the accumulation manually.
Someone · · focus · HN ↗
futune · · focus · HN ↗
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 · · focus · HN ↗
hyperhello · · focus · HN ↗
el_oni · · focus · HN ↗
But for unioning a bunch of spark dataframes together i think
is much nicer than People just get a bit funny, especially now you have to import it from functoolstehnub · · focus · HN ↗
olivewong · · focus · HN ↗
ducaale · · focus · HN ↗
- 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] <a href="https://fsharpforfunandprofit.com/posts/monoids-without-tears/#what-use-are-monoids-to-a-programmer" rel="nofollow">https://fsharpforfunandprofit.com/posts/monoids-without-tear...
rspeele · · focus · HN ↗
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 · · focus · HN ↗
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.
seanw444 · · focus · HN ↗
jdougan · · focus · HN ↗
bsnpApproved := tvShows reject: [ :eachShow | eachShow hasNaughtyContent ].
dreamcompiler · · focus · HN ↗
(It also has remove-if-not but that's deprecated and if you use it your code smells.)
shawn_w · · focus · HN ↗
Scheme has `filter` and `filter-not` in the SRFI-1 list library. Both of which can easily be written using a fold to bring this vaguely on topic.
kazinator · · focus · HN ↗
dreamcompiler · · focus · HN ↗
As for deprecation, several of the '-if-not' functions were deprecated because the committee felt the 'complement' function accomplished that task better.
kazinator · · focus · HN ↗
If you want keep-if, you don't want to use a different function, which forces you to complement your predicate. If you want (keep-if #'redp jellybean-list) to keep the red jelly beans, you don't want to write (remove-if (complement #'not-red-p) jellybean-list). If keep-if has a silly name remoe-if-not, you might nonetheless prefer (remove-if-not #'redp jelly-bean-list).
Shims like complements are ugly, and compilers won't optimize through them for arbitrary function definitions (whose source code is not even in scope), so it is good to have both keepers and removers. Heck, it's useful to have a function which does both in one pass returning two values: the filtrate and the retentate.
pavlov · · focus · HN ↗
listenallyall · · focus · HN ↗
dcminter · · focus · HN ↗
NooneAtAll3 · · focus · HN ↗
dcminter · · focus · HN ↗
SamBam · · focus · HN ↗
And it's a doubly-good analogy, because I have occasionally gotten that confused in real-life as well. Twice in the past ten years I've had a stock boil away for three hours, and then set a colander in the sink and poured it through, only to watch my beautiful stock swirl down the drain because motor-memory made me forget that I wasn't draining pasta but should have put the colander in a bowl...
dcminter · · focus · HN ↗
reddit_clone · · focus · HN ↗
C++ std::remove.
I would never have guessed what it does exactly. (It moves elements that match the filter to the front, and moves the end-marker forward. Leaves all the elements in the collection. You need to erase them yourself. )
mitxela · · focus · HN ↗
anitil · · focus · HN ↗
andrekandre · · focus · HN ↗
`filter(where:)` like in swift...?
mikebenfield · · focus · HN ↗
andrekandre · · focus · HN ↗
what about `exclude()` or `keep()`?
in those cases its less ambiguous (to me anyways) that returning true means 'yes' to 'keep' or 'exclude', whereas saying yes or no to filter is like 'filter to exclude or include?'
thats my take anyway
quaverquaver · · focus · HN ↗
layer8 · · focus · HN ↗
saghm · · focus · HN ↗
brabel · · focus · HN ↗
arnsholt · · focus · HN ↗
saghm · · focus · HN ↗
rmunn · · focus · HN ↗
thaumasiotes · · focus · HN ↗
In Common Lisp both functions exist, under the names `remove-if` and `remove-if-not`.
ihumanable · · focus · HN ↗
I think since we have a pair of them and reject is so obvious it helps me remember which way filter works.
I think Enum.keep and Enum.reject might be a better pair, but I've used them enough to internalize it now
zelphirkalt · · focus · HN ↗
mitxela · · focus · HN ↗
bawolff · · focus · HN ↗
APIs should make sense inherently. An IDE can band-aid a bad design, but that doesn't make it a good design.
atherton94027 · · focus · HN ↗
mitxela · · focus · HN ↗
brabel · · focus · HN ↗
jumpingscript · · focus · HN ↗
saghm · · focus · HN ↗
mamcx · · focus · HN ↗
ingonealan3 · · focus · HN ↗
karmakurtisaani · · focus · HN ↗
ketzu · · focus · HN ↗
Reduce can write transforms, filters and whatever else.
(or you could turn it into a tree)ketzu · · focus · HN ↗
Bad example:
Reduces the list to another list three times as long.It's a reduction in the sense of a transformation (also often seen in complexity theory), not in the "this makes this smaller" everyday usage that I think about first.
EFreethought · · focus · HN ↗
nextaccountic · · focus · HN ↗
In rust iterators there's both fold (you supply the initial value) and reduce (it uses the first element as the initial value, doesn't work on empty iterators)
<a href="https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.fold" rel="nofollow">https://doc.rust-lang.org/std/iter/trait.Iterator.html#metho...
<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...
kccqzy · · focus · HN ↗
kccqzy · · focus · HN ↗
I find this slightly easier to remember than other languages. In contrast most other languages do not simultaneously provide a left fold and a right fold, so they do not consider this aspect, making things more difficult to remember.
That said I totally agree this requires more brainpower to read and write than map or filter. For this reason I have sometimes refactored code to use foldMap instead of foldr or foldl', so one no longer needs to think of the direction of the fold or the order of arguments.
pash · · focus · HN ↗
internet_points · · focus · HN ↗
arialdomartini · · focus · HN ↗
<a href="https://arialdomartini.github.io/fold-mnemonics" rel="nofollow">https://arialdomartini.github.io/fold-mnemonics
bananaflag · · focus · HN ↗
mcv · · focus · HN ↗
But it hurts readability. If you're going to do it, at least don't use it anonymously, but give it a name that clearly describes what's going on.
But even then, there can be hidden performance traps. I've often seen javascript that used reduce and created the new accumulator by using a spread on the old accumulator and adding the new one: `[...acc, newValue]`. But that spread is another iteration inside a loop, turning it from O(n) to O(n^2). A for loop where you append it is much faster.
shawn_w · · focus · HN ↗
Luckily the functional languages I use the most are sane in that respect.
cben · · focus · HN ↗
d--b · · focus · HN ↗
and in many case the accumulator is a tuple, and in many cases you need to know the length of the collection ( like average)
all in all, it’s a lot just to avoid a for loop.
zzo38computer · · focus · HN ↗
I guess names as SELECT and WHERE are like SQL (although SQL works differently than other programming langauges).
thaumasiotes · · focus · HN ↗
I don't understand. Map takes input of type a and size n and returns output of type b and size n.
Filter takes input of type a and size n and returns output of type a and size ≤ n.
They look nothing alike?
dmi · · focus · HN ↗
throw310822 · · focus · HN ↗
s-zeng · · focus · HN ↗
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 · · focus · HN ↗
Care to explain?
polonbike · · focus · HN ↗
theamk · · focus · HN ↗
grebc · · focus · HN ↗
I feel like this is a case of personal preference over actual issue.
adastra22 · · focus · HN ↗
grebc · · focus · HN ↗
theamk · · focus · HN ↗
grebc · · focus · HN ↗
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.
theamk · · focus · HN ↗
grebc · · focus · HN ↗
You're not the OP, you/me we're only guessing what he/she might've meant.
d--b · · focus · HN ↗
That said, I personally don’t think it’s smelly at all.
[deleted] · · focus · HN ↗
[deleted]
nextaccountic · · focus · HN ↗
Fold is a recursion scheme
More complicated recursion schemes are progressively harder to read. Probably not great if you are not doing code golf
snackbroken · · focus · HN ↗
mrkeen · · focus · HN ↗
snackbroken · · focus · HN ↗
IceDane · · focus · HN ↗
sigbottle · · focus · HN ↗
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 · · focus · HN ↗
[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 · · focus · HN ↗
ndriscoll · · focus · HN ↗
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.
adastra22 · · focus · HN ↗
ndriscoll · · focus · HN ↗
adastra22 · · focus · HN ↗
ndriscoll · · focus · HN ↗
adastra22 · · focus · HN ↗
ndriscoll · · focus · HN ↗
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 being obtuse, that's you wandering into the wrong conversation.
antonvs · · focus · HN ↗
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)
txhwind · · focus · HN ↗
mcphage · · focus · HN ↗
It's always worthwhile to consider what the result will be when you pass in an empty list.
snackbroken · · focus · HN ↗
Lvl999Noob · · focus · HN ↗
mcphage · · focus · HN ↗
globular-toast · · focus · HN ↗
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.
chubot · · focus · HN ↗
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.
<a href="https://docs.python.org/3/library/functools.html#functools.reduce" rel="nofollow">https://docs.python.org/3/library/functools.html#functools.r...
taeric · · focus · HN ↗
That is, why couldn't they have done the essentially same trick that you reference for += with reduce?
kelipso · · focus · HN ↗
justonceokay · · focus · HN ↗
bmandale · · focus · HN ↗
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.
Maxatar · · focus · HN ↗
s = s + "foo"
and
s += "foo"
Since 2005 with the release of Python 2.4:
<a href="https://docs.python.org/3/whatsnew/2.4.html" rel="nofollow">https://docs.python.org/3/whatsnew/2.4.html
kenferry · · focus · HN ↗
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 reliability that the absolute fastest possible result the compiler can produce. 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.
bjourne · · focus · HN ↗
Maxatar · · focus · HN ↗
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:
<a href="https://docs.python.org/3/whatsnew/2.4.html#optimizations" rel="nofollow">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.
<a href="https://www.artima.com/weblogs/viewpost.jsp?thread=98196" rel="nofollow">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.
chubot · · focus · HN ↗
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 is 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 a bit, so he settled for banishing reduce() to the stdlib.
vhcr · · focus · HN ↗
<a href="https://github.com/python/cpython/commit/a70b19147fd163744be34745d393af7be603629f" rel="nofollow">https://github.com/python/cpython/commit/a70b19147fd163744be...
hn_go_brrrrr · · focus · HN ↗
chubot · · focus · HN ↗
rienbdj · · focus · HN ↗
j2kun · · focus · HN ↗
paradox460 · · focus · HN ↗
chubot · · focus · HN ↗
Digit-Al · · focus · HN ↗
Zak · · focus · HN ↗
The footgun isn't `reduce` in particular, but failing to use `join`.
edflsafoiewq · · focus · HN ↗
ndriscoll · · focus · HN ↗
edflsafoiewq · · focus · HN ↗
ndriscoll · · focus · HN ↗
edflsafoiewq · · focus · HN ↗
ndriscoll · · focus · HN ↗
Zak · · focus · HN ↗
vhcr · · focus · HN ↗
edflsafoiewq · · focus · HN ↗
OJFord · · focus · HN ↗
TZubiri · · focus · HN ↗
-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
anitil · · focus · HN ↗
Izkata · · focus · HN ↗
z500 · · focus · HN ↗
semiinfinitely · · focus · HN ↗
mahboi · · focus · HN ↗
mcphage · · focus · HN ↗
adverbly · · focus · HN ↗
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 · · focus · HN ↗
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 · · focus · HN ↗
So I love reduce, and have for many years.
jakub_g · · focus · HN ↗
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 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.
Terr_ · · focus · HN ↗
bunderbunder · · focus · HN ↗
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.
OkayPhysicist · · focus · HN ↗
bunderbunder · · focus · HN ↗
skybrian · · focus · HN ↗
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.
fatih-erikli-cg · · focus · HN ↗
[dead]
dochne · · focus · HN ↗
While not as functionally pure, I always appreciate the Ruby each_with_object <a href="https://ruby-doc.org/3.4.1/Enumerable.html#method-i-each_with_object" rel="nofollow">https://ruby-doc.org/3.4.1/Enumerable.html#method-i-each_wit... as a more pleasant interface for it.
agentultra · · focus · HN ↗
“We can’t have map in our codebase, we need to be able to fire 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 · · focus · HN ↗
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 · · focus · HN ↗
I really like taking the implementation away from the call site, so that the call site reads
(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 · · focus · HN ↗
Honestly, sounds like the reviewer failed the interview, not the other way around.
baxuz · · focus · HN ↗
anyfoo · · focus · HN ↗
the_other · · focus · HN ↗
However, I agree it might be less performant, and it's a certain kind of thinking that isn't quickly grokked (and doesn't have to be). I deliberately tried to write my story about the interview so as to make it sound like there's positives and negatives to both positions expressed. _I_ have a preference for that functional style, but I know it's not for everyone. That's totally fine.
xp84 · · focus · HN ↗
Wow. That is the kind of monkey business that would have me running for the exits. Yikes.
Joker_vD · · focus · HN ↗
xp84 · · focus · HN ↗
But if someone's 28 and doing that stuff in Python or JavaScript, they're a bit of a loon, imho.
kenferry · · focus · HN ↗
I agree, but I think that’s kind of the objection. It can be tempting to write your code as little brainteasers but…
catapart · · focus · HN ↗
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 · · focus · HN ↗
Aggregate, accumulate, combine for example.
xp84 · · focus · HN ↗
diegof79 · · focus · HN ↗
collection inject: aValue into: aBlock
jdougan · · focus · HN ↗
#(1 2 3 4 5 6 7) inject: 10 into: [ :sum :each | sum + each squared ].
The way I remember the order is it reflects assignment.
adamddev1 · · focus · HN ↗
anitil · · focus · HN ↗
internet_points · · focus · HN ↗
anitil · · focus · HN ↗
reddit_clone · · focus · HN ↗
'for' loop is mutating.
Using 'reduce', you can do the same functionally. In some (somewhat) purely functional languages, there is no choice.
layer8 · · focus · HN ↗
I also agree that a for loop is often clearer.
mkehrt · · focus · HN ↗
This is what reduce does, though? It reduces a list to a single thing. It seems like you're thinking of filter.
scotty79 · · focus · HN ↗
combine, accumulate it aggregate would have way more use.
altruios · · focus · HN ↗
mahboi · · focus · HN ↗
mahboi · · focus · HN ↗
This is also assuming we're talking about regular code and not an actual map-reduce framework like Spark.
Terr_ · · focus · HN ↗
1. It's cheaper/faster at communicating intent to humans reading your code. Since a reduce call can do all sorts of interesting things, people need to stare harder to realize "oh, it's just doing a a map and filter together."
2. Things are easier to debug. I can vet the process of transformation (and its intermediate results) and then vet the process of excluding some of those results.
___
[0] <a href="https://en.wikipedia.org/wiki/Law_of_triviality" rel="nofollow">https://en.wikipedia.org/wiki/Law_of_triviality
[1] <a href="https://www.smbc-comics.com/comic/noun" rel="nofollow">https://www.smbc-comics.com/comic/noun
xdavidliu · · focus · HN ↗
While we're on the subject, can someone explain to me why in Rust, you need to annotate the type when you call .sum() on an iterable? For example
That works, but fails if I replace `p: i32` with `p` or `p: i64`, and I cannot find a satisfactory answer in any thread or llm. The obvious question is why the compiler cannot infer the type from the element type of the container, and the naive response to that is for flexibility summing into a bigger type. But in that case, why would `p: i64` be rejected? And what other type is allowed besides i32?antfarm · · focus · HN ↗
A clarifying moment on the same level as when I finally understood how recursion and pointers work in 1995 in my first semester CS class, two concepts I had only ever read in programming books about, but not been able to understand on my own.
In 2024 I did Advent of Code in Swift, without using mutable state, custom data types or loops, and used reduce rahther generously. [1]
[1] <a href="https://github.com/search?q=repo%3Aantfarm%2FAdventOfCode2024+reduce&type=code" rel="nofollow">https://github.com/search?q=repo%3Aantfarm%2FAdventOfCode202...
pmontra · · focus · HN ↗
Filter picks values according to a rule. It's a select from where condition. Maybe not as easy as map but familiar.
Reduce is, what? Even the name is ill fated. Who wants to be reduced? Hence, harder to understand and probably not as common as the other two.
kevinwang · · focus · HN ↗
The other two can be simply expressed as a list comprehension, but afaik you can't with reduce (and if you can, it's probably awful).
felizuno · · focus · HN ↗
TypeScript basically ruined reduce for me though, so there is that.
tantalor · · focus · HN ↗
ndriscoll · · focus · HN ↗
tantalor · · focus · HN ↗
ndriscoll · · focus · HN ↗
min is a generic method. All it knows is it has a Stream<T> and a function taking two Ts. The only thing it can do is plug in Ts it gets from the stream.
very-old-sw · · focus · HN ↗
very-old-sw · · focus · HN ↗
odo1242 · · focus · HN ↗
Bayard_ne · · focus · HN ↗
grommet_kit · · focus · HN ↗
keychera · · focus · HN ↗
RomanKornev · · focus · HN ↗
Reduce can approximate anything, that doesn't mean we should use it.
My favorite antipattern is
Like, why? Not only is this ridiculously inefficient O(N^2), it's also longer and less understandable than "build a new map" version.wingman-jr · · focus · HN ↗
int_19h · · focus · HN ↗
tehnub · · focus · HN ↗
remywang · · focus · HN ↗
jay_kyburz · · focus · HN ↗
I like boring code.
lkuty · · focus · HN ↗
hibikir · · focus · HN ↗
Every industry language keeps gaining more and more functional features: Many a new Java version is adding a bunch of scala features with worse syntax. But we don't train people on functional programming at all, so by the time they've built their instincts, passing functions makes no sense to them, immutability is alien, and the idea of a pure function seems irrelevant to them. Thus, they don't get exposed to the building blocks that make reduce seem simple. We always teach them recursion, but the rest? Too little, too late.
I could tell you of a bunch of ways to simplify the signature by, say, mandating that one passes a monoid or something like that, but while the signature would be easier, the very same people that are only used to imperative OO will not have an easier time, because they might have studied 2 years of calculus, but they've never even smelled abstract algebra. You can walk out of not just a programming bootcamp, but many a computer science degree without learning a word of this. Therefore, it all remains complicated.
yen223 · · focus · HN ↗
runeblaze · · focus · HN ↗
I don't know that's a safe assumption tbh. Try throwing them some chapter 2 exercises from any category theory textbook.
jan_m_savage · · focus · HN ↗
[dead]
elgertam · · focus · HN ↗
Incidentally, reduce is also powerful enough to implement both map and filter in terms of itself, though that's more of a teaching exercise than a good recommendation.
I mostly interpret it as of the same spirit with those who oppose proper tail calls because it "ruins" their debugging stack traces.
txhwind · · focus · HN ↗
ggorlen · · focus · HN ↗
In cases when reduce is required because (for example) JS doesn't have a sum function, it should be kept simple. `arr.reduce((acc, el) => el + acc, 0)` is acceptable if lodash _.sum() is not available.
In cases when reduce is required because the higher-level operations like map/filter aren't flexible enough, decompose the reduction operation into simpler steps and use map/filter with multiple passes, or write a traditional for..of loop.
This principle also explains why enhanced/range/of loops are preferred over counter-based `for` loops, and counter-based loops over `while`. Technically all loops can be handled by `while`, but it's seldom needed because enhanced loops handle the common case with the cleanest syntax. Reduce/while/counter-based `for` loops are antipatterns where higher-level, less powerful abstractions exists.
[1]: <a href="https://wiki.c2.com/?PrincipleOfLeastPower" rel="nofollow">https://wiki.c2.com/?PrincipleOfLeastPower
marcta · · focus · HN ↗
FlameWolf · · focus · HN ↗
ggorlen · · focus · HN ↗
arr.reduce((acc, el) => { if (el % 2 === 0) { acc.push(el * 2); }
}, []);over
arr.filter(e => e % 2 === 0).map(e => e * 2)
The only advantage of the reduction as I see it is performance, but this is highly dubious and would need to be profiled for proof (I've never seen this matter in practice). And if perf does matter, a for..of loop would be clearer and one-pass:
const result = []; for (const el of arr) { if (el % 2 === 0) { result.push(el); } }
Exercises like this illustrate why technical verbal job interviews are useful in the age of LLMs--micro-taste preferences like this seems high signal (albeit not something I'd solely base a decision on).
FlameWolf · · focus · HN ↗
QuercusMax · · focus · HN ↗
ema · · focus · HN ↗
codethief · · focus · HN ↗
Meanwhile, if I see reduce(), "anything" could happen. (Well, of course not anything but the set of possible reducers is surely much larger.) So entropy is high.
Avoid high-entropy constructs in your code. Try to keep entropy as low as possible. (For the same reason, code with a principled approach regarding side effects is a lot better than code where any function could mutate global state at any given time.)
timtas · · focus · HN ↗
I like reduce, and I’ve never gotten pushback doe using it. Id be willing to use other functional method chains. But I would never listen to anyone who suggested using a procedural loop instead.
myaccountonhn · · focus · HN ↗
vova_hn2 · · focus · HN ↗
Two notes:
1. reduce if a part of functional programming vocab, so, obviously, a Clojure dev has to internalize it to be able to use the language properly. For other mentioned languages is not that necessary.
2. As a (mostly) Python dev, I think that list comprehensions and generator expressions are much easier to read and understand than map and filter. Although, people coming from other languages and having limited experience with Python specifically might disagree with me. Perhaps, we should think about inventing some nice syntax sugar that around the concept of `reduce`ing and `fold`ing, similar to what list comp/gen expr in Python did to concepts of `map`ing and `filter`ing.
vova_hn2 · · focus · HN ↗
First, I will need to steal a "consume" function from Itertools Recipes [0]:
Isn't it a bit weird, that the fastest and easiest way to consume an iterator entirely is to feed it to a zero length deque? It is weird, but it was just an apéritif, lets move to the main course: This is the line where the actual `reduce`ing happens: Basically, we use the fact that a "walrus" expression has a side effect and we just throw away the actual results of the iterator, because we don't need them.Is it more readable then normal reduce? I'm not sure. If I seen it in the actual production code, it would certainly raised my eyebrows. It is not a part of the normal Python "vocab" - a set of idioms that are considered "pythonic" and that you expect every Python dev to intuitively understand, so I would be very cautious in using it in the code that is intended to be read by other people.
Why did I do it? I don't know, just a fun "what if?" thought experiment.
[0] <a href="https://docs.python.org/3/library/itertools.html#itertools-recipes" rel="nofollow">https://docs.python.org/3/library/itertools.html#itertools-r...
vova_hn2 · · focus · HN ↗
fatbird · · focus · HN ↗
_moof · · focus · HN ↗
ema · · focus · HN ↗
Joker_vD · · focus · HN ↗
So in effect, as some other commenter said, it's just the loop with worse syntax.
dwoldrich · · focus · HN ↗
I also like Lodash'es transform[1]. It's like reduce, but expressly for transforming one collection to another. The signature is a slightly different from reduce in that the accumulator is a collection that is passed as an argument to the iteratee who is expected to mutate the accumulator with no need to return it. This frees up the return value from the iteratee for a new purpose: if the iteratee returns a boolean false, then transform early outs. I have used that feature more than once!
[1] <a href="https://lodash.com/docs#transform" rel="nofollow">https://lodash.com/docs#transform
globular-toast · · focus · HN ↗
asgr · · focus · HN ↗
gg582 · · focus · HN ↗
#if 0 these_lines_are(); not_executed(); #endif
but most of the cases people just use
/* comments * these_lines_are(); * not_executed(); * * end comment */
Then, why?
#if 0 #endif
looks clear and it definitely says how a computer skips many lines.
But we just don't use it because it implies low-level knowledge "that every C developers have"
draven · · focus · HN ↗
gg582 · · focus · HN ↗
molvqingtai · · focus · HN ↗
Anoian · · focus · HN ↗
The standard linter plugin eslint-plugin-unicorn even has a rule "no-array-reduce" that is part of the recommended config, which means most people using this plugin will have no reduce in their codebases:
<a href="https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-reduce.md" rel="nofollow">https://github.com/sindresorhus/eslint-plugin-unicorn/blob/m...
pjmlp · · focus · HN ↗
flossly · · focus · HN ↗
I never met so many different variants of the `map` or `filter` function in Haskell.
Maybe this shows, in a different way from the reasons in the article, why reduce is harder than map/filter.
iforgotmypasswo · · focus · HN ↗
However, in those times of yore, I would often go back and remove it before committing. Unless you’re surrounded by other clever people, or it’s a personal project, you’re leaving behind some very elegant looking anxiety for the less gifted developers. Usually just to save one or two lines of code.
FacelessJim · · focus · HN ↗
``` x = mapreduce(f, r, arr, init) ```
equivalent to ``` x=init for e in arr r(x, f(e)) end ```
Of course, if r is ever something different than a simple operator, slap yourself. But otherwise it’s an absurdly powerful construct.
gorjusborg · · focus · HN ↗
Filter and map are easy because you can make more assertions about them without inspection:
- they take a list-like as input
- they take a function as input
- they return a list-like as output
As opposed to reduce:
- it takes a list-like as input
- it takes a function as input
The output type is not fixed, and its behavior is not fixed. If you pass the right function, reduce is filter, or map, or something else we've never seen.
amluto · · focus · HN ↗
Reduce could mean one of quite a few things. (How many folds does Haskell have? At least six.) And most of them are, in a sense, so trivial that there is no real benefit to spelling it “fold” or “reduce” instead of just calculating it explicitly. (Okay, one can sometimes lazily fold a lazy list, for example by applying the identity. This is not the normal case, especially in eager languages, which is most of them.) And, if you just write a loop instead of “reduce”, then it’s more obvious what’s going on and why the code might be inefficient.
IMO the actually interesting case of reduce is the associative case, which can be parallelized. This is not the default in most languages.
cmrx64 · · focus · HN ↗
randyrand · · focus · HN ↗
Reduce means to take away.
But reduce isn't taking something away. It's squeezing something together. Combining multiple things into one. Reduce is poor verb for that.
It should've been called squash. Just like squash-merge.
romellem · · focus · HN ↗
- <a href="https://github.com/romellem/eslint-plugin-no-spread-in-reduce" rel="nofollow">https://github.com/romellem/eslint-plugin-no-spread-in-reduc...
It catches code that looks like this: