4 ms·
I assume the author is talking about `fold`, as in `[A] -> B -> ((B,A) -> B) -> B`, and not what I often think of as reduce as `[A] -> ((A,A) -> A) -> A`. `fol
by japgolly 11d ago
I assume the author is talking about `fold`, as in `[A] -> B -> ((B,A) -> B) -> B`, and not what I often think of as reduce as `[A] -> ((A,A) -> A) -> A`.
`fold` is awesome and super useful. It's the easiest and most convenient way to turn a collection into a single value. Put me anecdotally in the opposite bucket.
- wannabe44 10d ago> `fold` is awesome and super useful. It's the easiest and most convenient way to turn a collection into a single value. You will eventually learn about something called "for loop", and it will be nice.
- t-3 10d agoThere are way more places where a simple typo will ruin you in a for loop than a reduce or fold or map. Using briefer abstractions in place of nested loops is almost always preferable.
- robrenaud 10d ago> Using briefer abstractions in place of nested loops is almost always preferable. Indeed, this is why everyone knows the J programming language.
- mrkeen 10d agoNah. It involves multiple passes and setting the answer to the wrong value before (hopefully) setting it to the right value. Plus it forces you out of whatever lazy/streaming paradigm you had going on. If your foldr produces a list, downstream can start consuming it in constant memory as long as you let it do its thing.
- 8note 10d agofold kinda does too, for setting the first combined value that you are assembling, and thus on an empty list you end up with that wrong value, same as the for loop
- mrkeen 10d agoNo, the sum of the first ten natural numbers is always 55. It is not "initialised" to some other number beforehand.
- bspammer 10d agojapgolly’s signature for reduce above is slightly wrong, it should be `[A] -> ((A,A) -> A) -> Maybe A`. I.e. there is no initial value to pass in, but the result is an Optional to handle the empty iterator case. That’s how rust does it, for example: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.reduce https://doc.rust-lang.org/std/iter/trait.Iterator.html#metho...
- KPGv2 9d agoIf your data should not be successfully folded if it's empty, you should've already parsed it as an Optional nonEmptyList instead of letting illegal states fly around for a while in your application.
- bspammer 9d agoYeah that's good application design, but those concerns aren't so relevant to the person writing the standard library for a language. You can certainly include a specialized version of reduce for nonEmptyLists which just returns A, but that doesn't change the fact that you have to return an Optional for a normal possibly-empty iterator if you want your reduce function to be non-partial.
- KPGv2 9d agoFor loops are not easier or more convenient than fold. fold sum 0 collection versus acc = 0 for x in collection: acc = acc + x or the even worse int acc = 0; for(int x = 0; x < collection.length; ++x) { acc += collection[x]; } You can read one line and know exactly what's happening in the fold example. In the Python and C++ examples, you have to scan more lines and there's way more opportunity for typos. A for loop gives you better memory management and speed, but the tradeoff only makes sense to me if you're doing embedded work or something. Otherwise, eat the .000000000001% speed loss to reduce the risk of logic errors, typos, etc. and to improve developer ergonomics.
- wannabe44 9d agoOnly real difference is that `fold` is denser. Both require prior knowledge to understand in their respective paradigms. Adding numbers like this is not common in real world code. Now let's say instead of adding x, you have too look up X in a cache with an additional "type" param and update a metric of cache hits (or misses). You have to define a free function to keep your fold readable and understandable. In for loop it's much easier to understand.
- KPGv2 5d ago>You have to define a free function to keep your fold readable and understandable. I agree. But you'd do that for a for-loop, too, unless you want a bloated for-loop. > In for-loop it's much easier to understand. I have to disagree there. You'd still be working with a free function, or you'd be working with a bloated for-loop body. Combining cache loopkups, metric tracking, etc. runs into SOC issues that IME for-loops just let imperative developers get away with until it comes time to test their code. Furthermore, free functions aren't bad. They're good. They're a self-documenting abstraction. Unless you name it `function_one` or something. Having my fold lambda do its primary business role but call `update_cache_and_metrics` makes it unnecessary for someone reading the flow of logic from even needing to go read the body of that free function.
- nightpool 10d agoI think part of the issue is that a lot of programming languages don't make a strong distinction between the two, and only provide the (more powerful) fold, but in a way that makes reduce operations harder to reason about (like OP said, with 0 types). Associativity also makes fold hard. It's not super trivial to know when you might need e.g. left fold vs right fold
- Chinjut 10d agoThese are pretty close to each other, to the point where I wouldn't bother strongly distinguishing them. Suppose we have foldr as in [A] -> B -> ((A, B) -> B) -> B, foldl as in [A] -> B -> ((B, A) -> B) -> B, and reduce as in [A] -> ((A, A) -> A) -> A. Then we have foldr list value operator = reduce [\b -> operator a b | a <- list] (.), foldl list value operator = foldr (reverse list) value (flip operator), and in the case of a finite non-empty list and associative operator, we have reduce list operator = foldr (tail list) (head list) operator = foldl (init list) (last list) operator. So these are all basically slight re-parametrizations of each other.
- kaoD 9d ago> Put me anecdotally in the opposite bucket. The fact that you wrote this comment with Hindley-Milner-ish notation already makes your an outlier.