13 ms·
Anecdotally, Programmers Dislike "Reduce"
- Glyptodon 12d agoIt'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.
- Skeime 14d agoI think this is because in an imperative language, `reduce` does not actually give you much over a `for item in collection` loop. With `map` and `filter`, you immediately learn something about the result (it's a list of the same length as the original, with each item only depending on the corresponding original item; it's a list containing some of the original elements unchanged and nothing else). This is useful, so `map` and `filter` are good. 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 14d agoI think that in every imperative language that offers `map`, `filter`, `reduce`, or similar, the written contract of this API should state that any higher-order function handed to it as an argument must be free from side effects. 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/Stream.html#reduce-T-java.util.function.BinaryOperator- https://docs.oracle.com/javase/8/docs/api/java/util/stream/S...
- Someone 14d agoEven though it makes print debugging harder, I think it would be better if the language enforced such a contract.
- Skeime 14d agoI mean, most of the code that I write would be side-effect free anyway. In an imperative loop, this would also be true except for updating local variables. If this is the case, `reduce` really is the same as a loop over a collection, except that the names for the state passed between iterations come out better. In the `reduce` version, you can name the parameters to the reducer, but often not the return values. As a reader, one needs to connect the return values to the parameters by position. (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 14d agoIn Rust these specifically take `FnMut`, a function which can update internal/borrowed state, rather than `Fn` which can't easily. In `map` or `filter` you shouldn't rely on the iteration order so that's not often useful – maybe something 'logically' stateless but which needs a mutable connection/threadpool/cache, or eg a counter which is really an ancillary reduction. There's even `inspect` which is explicitly for such side effects. In `fold`, the order is guaranteed and you could use it for a state machine, a fiddly `zip` with other mutable iterators, etc – something you need to perform the reduction, but which isn't really an output, I think you could reasonably write either .fold(init, move |acc, x| {…}) // or .fold((state, init), |(state, acc), x| {…}).1
- KPGv2 11d ago> I think that in every imperative language that offers `map`, `filter`, `reduce`, or similar, the written contract of this API should state that any higher-order function handed to it as an argument must be free from side effects. 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. I also don't see why a reduce should be side-effect free. There's nothing wrong with having a type def't for reduce be base.data.List.foldLeft : (b ->{e} a ->{e} b) -> b -> [a] ->{e} b Where {e} indicates a side effect. Here, the reducer can have side effects, and those side effects propagate to the result of the fold/reduce.
- Someone 14d agoI think what makes reduce less popular is that it takes two lambdas: - 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 14d agoI was going to write a question asking if reduce is the thing I know as accumulate (I think I picked this up from SICP). But then I went to wikipedia, and it seems that an even more common name is fold. 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 14d agoAnd in some contexts you have the subtle distinction that fold is linear and reduce requires an associative operation and an identity element (aka a monoid)
- hyperhello 14d agoFor can have another set of variables in the header too. You can simulate it more readably even if you need to call the lambda.
- el_oni 14d agoIve only used reduce at work half a dozen times and it does raise an eyebrow each time. 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 functools
- tehnub 11d agoCame here to comment this. I work on a team of functional-adverse devs, but they all happily use this one.
- olivewong 14d agoI agree, but I think a lot of it is variable name abuse on the accumulator, making it unclear. I've seen a lot of single letter or worse, a coworker who named it "cum" for short which is super not okay
- ducaale 14d agoI find `reduce` useful for operations where: - 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-tears/#what-use-are-monoids-to-a-programmer https://fsharpforfunandprofit.com/posts/monoids-without-tear...
- rspeele 13d agoMap and filter usually have only one arg and if they have 2, the 2nd is almost always a 0-based index. They look identical in most languages, even when Microsoft chooses to call them Select and Where. 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 12d ago> … to call them Select and Where. 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 12d agoI've never run into a generic "filter" function which keeps only the non-matching elements.
- jdougan 11d agoSmalltalk has #reject: which does that. You could, of course, just wrap a not around the test in the closure, but sometimes reject with a well-named predicate is easier to read. bsnpApproved := tvShows reject: [ :eachShow | eachShow hasNaughtyContent ].
- dreamcompiler 11d agoCommon Lisp's filter is remove-if which works this way. (It also has remove-if-not but that's deprecated and if you use it your code smells.)
- chubot 12d agoRelated 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. https://docs.python.org/3/library/functools.html#functools.reduce https://docs.python.org/3/library/functools.html#functools.r...
- semiinfinitely 12d agoyou guys still reading and review code with ur eyes and brain?
- deleted 12d ago[deleted]
- ChrisMarshallNY 12d agoI like it, but I don't use it anywhere near as much as other built-in closures. 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 12d agoI 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.
- norir 12d agoAnywhere 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.
- juancn 12d agoI 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.
- deleted 12d ago[deleted]
- WesolyKubeczek 12d agoI like neither of the three and prefer for loops and if statements instead. Yay for shallower stacks!
- franey 12d agoAt 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: 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 12d ago> accumulator 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 12d agoThat'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.
- BariumBlue 12d agoIt literally may be a syntax thing, but I too can never remember the exact arguments to put where so I never use it. I think if `reduce` looked more functional or more like Erlang code, it'd be easier to read and digest.
- yojo 12d agoIn 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.
- patwolf 12d agoI'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.
- mcphage 12d agoI like it, but don't care for the name. I find it easier to think about in terms of an accumulator.
- adverbly 12d agoSpeaking as someone who often tries to reduce my use of reduce by replacing it with map and filter where possible, for me, falling back to reduce is analogous to falling back to a while loop or a for loop: I avoid it if I can. 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 12d agoIt's simple really: looping is something we've all done a ton. Map is just a specialized version of something you do all the time, made better/simpler: what's not to like (and learn quickly)? 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 12d agoOne of the books that most affected my understanding, ability, and joy of programming was Mark Jason Dominus' "Higher Order Perl." So I love reduce, and have for many years.
- jakub_g 12d agoI dislike reduce because people sometimes do wild things in the callback that take a lot of mental effort to understand. 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.
- Terr_ 12d ago> sometimes do wild things in the callback that take a lot of mental effort to understand And even when they don't, you have to spend effort to determine that they aren't.
- bunderbunder 12d agoI've seen a lot of technical points about reduce, all of which are true. 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 12d agomap() and reduce() are equivalent in terms of jargon, IMO. Map also suffers from name collision with dictionaries/objects/whatever your language wants to call a key-value pairing.
- deleted 11d ago[deleted]
- skybrian 12d agoIt would be clearer if the operation were part of the name. The most common operations have good names, like sum(), product(), concat(), and so on. 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 12d ago[dead]
- dochne 12d agoI like it, but it is by far the most ungainly of the three with the most footguns in it's usage. 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_with_object https://ruby-doc.org/3.4.1/Enumerable.html#method-i-each_wit... as a more pleasant interface for it.
- agentultra 12d agoIn my experience, it depends a lot on the language and the folks you work with. I’ve gotten an eyebrow and a stern talking to for using ‘map’ in JavaScript once. Some people are die-hard about statements and keywords and imperative programming and their world view and be myopic. “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 12d agoReduce introduces state (accumulator), unlike map/filter which normally are used for immutability. 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 12d ago(JS/TS is my main language) I love reduce()! It's a hammer/nail method for me. Everything looks like a problem solvable by reduce. (I'm often wrong on that, but I quite enjoy learning why by trying). 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 12d ago> 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. Honestly, sounds like the reviewer failed the interview, not the other way around.
- baxuz 12d agoNah. Reduce is less performant and less readable than a regular loop. In my anecdotal experience, only people who want to appear smart and minimize the number of characters prefer reduce.
- anyfoo 12d agoI don't know about JavaScript, but those statements are definitely not universally true in other languages.
- the_other 11d agoReduce definitely does not reduce character count. It's the loop-hacking that does that. 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.
- catapart 12d agoFor me it's the name[0]. map puts out an array that has been mapped from another array. filter puts out an array that is a filter of the input array. both of those are always true. reduce, on the other hand, may put out a reduction of the input array (probably most of the time), but the fact that it may not means that what is happening is not actually a reduction. In languages like js/ts, you don't even have to return anything of the same type as the input array's elements. You could literally "reduce" and array of integers to a cancellation token, or a state object, or anything else. 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 12d agoIt doesn't have to be exactly correct. It just need to express intuitively the most common use(es?). Aggregate, accumulate, combine for example.
- xp84 12d agoRuby adds an alias `inject` for reduce. The #1 way I see it used there is like this: some_hash = my_array.inject({}) {|accumulator, item| ... } But I honestly very rarely use it (by either name) outside of a couple of pasted-in snippets (that I can't recall right now) where the strategy fits exceptionally well, probably because of the dumb reason that I tend to forget which block argument comes first (accumulator, or iterated item)! With other two-item argument lists such as `Hash#map` it being `key, value` makes sense, but with reduce/inject I don't see an obvious order. And I guess I learned before it was likely that some kind of AI autocomplete would be filling the args in for me.
- scotty79 12d agoAlternative theory - reduce is badly named. combine, accumulate it aggregate would have way more use.
- altruios 12d agoI'm the weird one here. In JS at least, I reach for reduce before map and filter in most cases. Often it is because I want the accumulator, particularly when I have a list of objects with various properties that I wish to sum together in a reduced object.
- deleted 12d ago[deleted]
- mahboi 12d agoReduce always makes me question the performance and order of operations. The most I'll do in Python is like sum(x[1] for x in args) which is map + reduce. And that's only if x[1] is a number. That's about it. No equivalent in JS. Whenever some JS code has map, I'm like why, and rewrite it as a loop. This is also assuming we're talking about regular code and not an actual map-reduce framework like Spark.
- Terr_ 12d agoSure, I'm up for some bike-shedding. [0][1] Unless performance demands otherwise, I prefer map+filter because: 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. _____ With respect to debugging, a sample form Elixir's REPL where the piping (|>) to the dbg() function reveals the intermediate state: iex(1)> [5,34,6,2,7,3,1] |> Enum.map(fn x -> x * x end) |> Enum.filter(fn x -> x < 10 end) |> dbg() [iex:4: (file)] [5, 34, 6, 2, 7, 3, 1] #=> [5, 34, 6, 2, 7, 3, 1] |> Enum.map(fn x -> x * x end) #=> [25, 1156, 36, 4, 49, 9, 1] |> Enum.filter(fn x -> x < 10 end) #=> [4, 9, 1] [0] https://en.wikipedia.org/wiki/Law_of_triviality https://en.wikipedia.org/wiki/Law_of_triviality [1] https://www.smbc-comics.com/comic/noun https://www.smbc-comics.com/comic/noun
- xdavidliu 12d agoreduce has complexity to handle the edge case of an empty iterable, and also for the case of a binary function with different types for inputs and outputs. That makes it harder to reason about and "uglier" than map and filter. People probably hate sum and product significantly less, both those also have the edge cases of empty iterable, in which case the natural result is 0 for sum and 1 for product sure, but of what type? 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 let p: i32 = [1i32, 2, 3].iter().sum(); println!("hello {}", p); 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 12d agoI remember finally getting what closures and reduce are when I learned Ruby in 2008 for my first Rails job. A pivotal moment on the same level as when I finally understood how recursion and pointers work in 1995 in my first semester CS classes (taught in Modula 2), two concepts I had only ever read about in programming books, 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] https://github.com/search?q=repo%3Aantfarm%2FAdventOfCode2024+reduce&type=code https://github.com/search?q=repo%3Aantfarm%2FAdventOfCode202...
- pmontra 12d agoMap maps a value into another value. It's a function call. Easy to understand. 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 12d agoIn python: I've always thought it's funny that of the list functions (map, filter, reduce?), reduce is the one that was removed, but is the only one that I occasionally reach for. (When I remember it doesn't exist, I'm usually happy to write the more readable three-line for loop.) 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 12d agoIDK, in JS I love reduce and think it is invaluable. If you don't care about closures, never used underscore/lodash, and have not written several hundred var self = this; then you don't share my pain. IMO fat arrow const/let kids don't know about walking uphill to school both ways. Also I agree with commenters who use prev instead of acc, it is much easier on my brain to use prev. TypeScript basically ruined reduce for me though, so there is that.
- tantalor 12d agoMy favorite gotcha is Java's `Stream.reduce(accumulator)` doesn't call the accumulator if your stream has zero or one elements. This is used for `min(comparator)` and `max(comparator)`. It's very funny when the comparator throws, but only when you have 2 or more elements.
- ndriscoll 11d agoHow is that a gotcha? If there aren't two elements how could you possibly expect a function with two arguments to be called? What would you call it with?
- tantalor 11d agoYou can easily write the code with only one-arg functions. Example: myStream.min(Comparator.comparingDouble((obj) -> { ... })); You might think: it will map over the objects and convert each object to an number, and take the object with the lowest value. Except that's not what it does. You'd be wrong!
- ndriscoll 11d agoComparator.comparingDouble returns a comparator, which is a two argument function, which is what min expects. Min can't know how your comparator is defined without reflection to do that kind of peeking, and as you point out, that kind of peeking would cause observable differences in behavior. 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 11d agoreduce with barriers is essential in parallel functional programming. See the CUDA thrust package.
- very-old-sw 11d agoReduce with barriers is essential (and amazingly powerful) in parallel functional programming languages like CUDA Thrust.
- odo1242 11d agoYea, reduce is most useful as a parallel operation, like in MapReduce (Or, at minimum, when the reduction operation is commutative)
- Bayard_ne 11d agoYeah, it usually adds cognitive load for anything beyond basic summation. Simpler to just use a good old `for` loop.
- grommet_kit 11d agoTotally get it. `reduce` feels like a hammer for every nail; often `map` or `filter` makes intent clearer for others.
- keychera 11d agoI came to like reduce when I learned clojure transducers. even in clojure, I always go for looping construct before transducer and then both reduce and transducer just clicked at the same time and I like reduce more now.
- RomanKornev 11d agoEvery single `reduce` can be replaced with a more intuitive `groupBy`, `partition`, `mapValues`, `keyBy`, etc. Reduce can approximate anything, that doesn't mean we should use it. My favorite antipattern is items.reduce( (acc, item) => ({ ...acc, [item.id]: item, }), {} ); 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 11d agoI think this along with the other answers discussing the difficulty remembering the specific arguments of `reduce` (especially when varying by language!) are key reasons. After reading this conversational thread, I think maybe Microsoft got it right with LINQ: - `Where` is perhaps more intuitive than `filter` - `Select` seems no worse than `map` by invoking SQL-like syntax - While `reduce` is preserved as `Aggregate`, provide `GroupBy` and other handy methods as the preferred methods. In the code I write, it's probably these other methods that get called 95+% of the time. Who wants to `Aggregate` when they can simply `Sum` for example?
- int_19h 9d agoLINQ was very intentionally designed to use SQL terminology for this because at the time (to remind, this was 2007), your typical C# or VB developer had no clue whatsoever about functional programming and the usual terminology there, while SQL knowledge would be much more widespread.
- tehnub 11d agoThis one doesn't seem expressible in those terms: from functools import reduce df = reduce(DataFrame.union, dfs)
- remywang 11d agoI’m so confused, how are you supposed to perform aggregation without reduce? This is like saying “I like plus and times, but I don’t like divide because it’s hard.” I mean sure, but you need it??
- jay_kyburz 11d agoYeah, but you never _need_ reduce. You can just have a boring loop. I like boring code.
- lkuty 11d agoNot in Elixir. Unless you use tail recursion to simulate the loop. I love reduce and the other functions of the Enum module, so I have 165 calls to reduce in my code base (plus 338 map and 116 filter). It's the swiss army knife of functional programming and I don't see any problem with its usage.
- hibikir 11d agoYou didn't get that complaint in Clojure, just like you wouldn't in Scala or Haskell, is that once you have any expectation that your users know a little bit of category theory, and possibly also thinking in types, it's all quite easy. Even fold is kind of easy, with the more complex signature. But passing [A][A,A =>A] kind of sucks for those that don't think of functional programming. and [A][A,B => A] is even worse. It's often bad enough to get people to build a comparator. 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 11d agoClojure specifically elevated the status of the reduce function because of transducers, a powerful but not very intuitive (imo) approach to composing functions.
- runeblaze 11d ago> once you have any expectation that your users know a little bit of category theory 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 11d ago[dead]
- elgertam 11d agoThe only part of "hard to read" that has ever made sense is that the callback takes multiple args and sometimes I can't remember the order of the initial value versus the accumulator. 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 11d agoIn imperative languages, it's a leaky abstraction not reducing the cognition overhead, compared with the plain loop.
- ggorlen 11d agoReduce is a good illustration of the principle of least power[1]: it's powerful, flexible, general and low-level and can technically achieve any combination of summation, map, filter, find/includes, some/any/every, etc. But reduce is misused if it's reimplementing patterns available in higher-level form. 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]: https://wiki.c2.com/?PrincipleOfLeastPower https://wiki.c2.com/?PrincipleOfLeastPower
- marcta 11d agoWhy is arr.filter().map() better than arr.reduce()? Doesn't arr.reduce() only loop once through the array?
- FlameWolf 11d agoExactly. If I need just filter/map/some etc., I use it. But if I need a combination of more than one, that's a job for reduce().
- ggorlen 11d agoSo you prefer arr.reduce((acc, el) => { if (el % 2 === 0) { acc.push(el * 2); } return acc; }, []); 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 don't recall seeing removing a pass like this matter in practice). And if perf does matter, a for..of loop would be clearer and one-pass, not to mention async-compatible: const result = []; for (const el of arr) { if (el % 2 === 0) { result.push(el * 2); } } Exercises like this illustrate why verbal technical job interviews are useful in the age of LLMs--a series of A/B taste preferences seems high signal and ripe for discussion: "Ah, so you're choosing reduce for perf... please describe a scenario you encountered where this made a measurable impact".
- myaccountonhn 11d agoI personally find recursive functions mentally easier to write than folds. Maybe because I can never remember the argument ordering and the inferred types throw me off.
- vova_hn2 11d ago> reduce is less elegant in languages I use, like JavaScript, Python, and Swift. In my blissful stint as a Clojure developer, I did not get this feedback. 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 it 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 11d agoActually, now that I think about it, with this new(-ish) (in)famous walrus operator and itertools recipes, I could sort of emulate reduce using gen expr First, I will need to steal a "consume" function from Itertools Recipes [0]: from collections import deque from itertools import islice def consume(iterator, n=None): "Advance the iterator n-steps ahead. If n is None, consume entirely." # Use functions that consume iterators at C speed. if n is None: deque(iterator, maxlen=0) else: next(islice(iterator, n, n), None) 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: lst = [1, 2, 3] acc = 0 consume((acc := acc + item for item in lst)) # this is the actual reduce print(acc) This is the line where the actual `reduce`ing happens: consume((acc := acc + item for item in lst)) 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] https://docs.python.org/3/library/itertools.html#itertools-recipes https://docs.python.org/3/library/itertools.html#itertools-r...
- fatbird 11d agoI had a client call me up and complain about my reduce code because it was hard to read and he couldn't tell what was going on. I broke through to him when I added comments to it that showed a particular data structure going in, and what was coming out. Once the transformation was clear, the apparent complexity was no longer a problem, leaving me to believe that the problem with reduce is almost entirely about legibility.
- _moof 11d agoWhat's hard to understand about it? It's just x = initial for y in collection: x = f(y, x)
- ema 11d agoIt's easy but it ain't simple. Because `f` can do arbitrary things to `x` you have to look at it just to know the general shape of the computation. Reduce gives you a lot of the flexibility of imperative programming but with that also a lot of the problems. Sometimes that's the right trade-off but it is good to be aware that it is a trade-off.
- Joker_vD 11d agoYou mean "f(x, y)", right? Half of the languages that have reduce/fold use this ordering, another half uses yours. So in effect, as some other commenter said, it's just the loop with worse syntax.
- dwoldrich 11d agoI am always happy when I find an opportunity to reduce or zip, so handy. 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] https://lodash.com/docs#transform https://lodash.com/docs#transform
- globular-toast 11d agoMost programmers aren't comfortable with higher-order functions, in my experience. Map and filter are special cases that they may have learnt, but other less common cases they don't understand. Also, in many languages reduce is hobbled by the fact operators aren't functions. I used it Common Lisp all the time, but it's awkward to use in, say, Python as the function you want is so often an operator. It's also more beautiful if the operators are n-ary like in CL, so the result of (reduce #'+ '()) is the same as (+), ie. 0.
- asgr 11d agoreduce is great - love it.
- gg582 11d agoSome conventions are socially made... In C, some uses #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"
- molvqingtai 11d agoI am using reduce to replace the nonlinear loop in the code instead of the for statement.
- Anoian 11d agoI am a typescript dev and I like reduce but also feel like I am the exception. 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: https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-reduce.md https://github.com/sindresorhus/eslint-plugin-unicorn/blob/m...
- pjmlp 11d agoNot really, programmers only educated in traditional imperative programming I would assert.
- flossly 11d agoReduce is like `fold` in Haskell right? Fold in Haskell has many variant, I forgot exactly which, but I remember there were many. 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 11d agoI liked reduce. You know, back when writing code was actually a thing we all did. 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 11d agoHave they heard of our lord and saviour “mapreduce”? 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 11d agoI would guess that a dislike for reduce overlaps with a preference with imperative languages. 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 11d agoOne issue I haven’t seen mentioned: map and filter have both obvious semantics and obvious implementations in the sense that (aside from possible laziness) there is really only one thing they could do, a parallel implementation is essentially semantically identical to a non-parallel implementation, and there are no potential numerical issues (used broadly in the sense of functions that are, say, ideally associative but not actually associative in real life). You apply the function to the input and you either collect or filter the results. 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 11d agothere are more interesting recursion schemes to traverse and aggregate with, but they are intricate to express without a powerful language and confuse small minds so aren’t worth often using. QI(I)Ts let you simultaneously declare how you witness and not just how you construct. https://github.com/passy/awesome-recursion-schemes https://github.com/passy/awesome-recursion-schemes
- randyrand 10d agoIMO it's primarily because it's a bad name. Names are important, as they reinforce your mental model. Reduce means to take away. But reduce isn't taking something away. It's combining multiple things into one -- squeezing multiple things together. Reduce is poor verb for that. It should've been called squash. Just like squash-merge.
- romellem 10d agoIt is too easy to write O(n^2) code using reduce, in fact I wrote an eslint rule to catch a common pattern I see other developers write. - https://github.com/romellem/eslint-plugin-no-spread-in-reduce https://github.com/romellem/eslint-plugin-no-spread-in-reduc... It catches code that looks like this: items.reduce((acc, item) => { return { ...acc, [item.id]: item }; });