6 ms·
Go people will do this and they'll be content: a := []int{1,2,3,4} it := slices.All(a) it = slices.Reverse(it) it = slices.Map(it) it = slices.Filter
by kubb 2y ago
Go people will do this and they'll be content:
a := []int{1,2,3,4}
it := slices.All(a)
it = slices.Reverse(it)
it = slices.Map(it)
it = slices.Filter(it, func(i int) bool { return i % 2 == 0 })
slices.ForEach(it, func(i int) { fmt.Println(i) })
I don't judge the Go enjoyers, but I prefer writing TypeScript to Go which says it all.
Type-inferred arrow lambda for function arguments would go such a long way in making this code nicer... And not make compilation slower at all.
it = slices.Filter(it, i => i % 2 == 0)
slices.ForEach(it, i => fmt.Println(i))
- jerf 2y agoNo, Go programmers would write a := []int{1, 2, 3, 4} out := []int{} for idx := range a { val := a[len(a)-idx-1] if mappedVal := Map(val); mappedVal % 2 == 0 { out = append(out, mappedVal) fmt.Println(mappedVal) } } In modern Go I might write a reverse iterator, that index is a bit hairy, which would cut out the 'val :=' line, but as there is not yet a standard library option for that I'll leave it out.
- kubb 2y agoMany Go programmers would claim this is cleaner and more understandable, unfortunately I'm not one of them.
- int_19h 2y agoThe bigger problem is that it's not efficiently composable. If your caller needs to additionally filter `out`, that's another loop with a copy.
- deleted 2y ago[deleted]
- thegeekpirate 2y agoThe proper term is deforestation https://en.wikipedia.org/wiki/Deforestation_(computer_science) https://en.wikipedia.org/wiki/Deforestation_(computer_scienc..., and I have seen Go libraries which do this
- nemo1618 2y agoah, that's what it's called! I did this in my compile-to-Go language, but I didn't know the name for it: https://github.com/lukechampine/ply?tab=readme-ov-file#supported-optimizations https://github.com/lukechampine/ply?tab=readme-ov-file#suppo...
- jerf 2y agoIn most imperative languages, writing .Map().Map().Filter().Map() is another full copy for each call anyhow. As a sibling notes, it is possible to "fix" that, but the fix is not even remotely free. It is quite complicated to do it generically. (Although there are other benefits; the most natural approach to that largely fixes my grumblings about refactoring I mentioned in another post.) Plus, in a for loop approach, it is not true that the caller may need another loop with a copy. They may just loop over the result and skip over the things they don't need. They only need a copy if they are going to pass that on to something else. A drum I can not stop banging on is that you can not just take a neat technique out of one language and slam it into another without examining the end result to make sure that you haven't murdered the cost/benefits tradeoff. You can slam together all the maps and filters and reduces you want in Haskell, and applicatives and monads and all the fun, due to a combination of the laziness and various safe optimizations like loop fusion. In an eager context that lacks loop fusion, going .Map().Map().Map().Map() has radically different performance implications. For instance, "take 10 $ map f list" in Haskell will only call "f" 10 times. .Map().Take(10) in most implementations will create the full array, however large it is, and slice ten off the end after that. In imperative languages, contrary to frequent claims from the functional programming crowd, for loops are actually often better in practice. The solution to their pathologies is to be aware of them and not do them. But it is far, far easier to get good performance out of a for loop in an imperative language than to contort one's code into a pale parody of functional programming.
- akkad33 2y ago> In most imperative languages, writing .Map().Map().Filter().Map() is another full copy for each call anyhow. Which imperative language? In Java and rust, two languages I know, all these operations are lazy until the final collect. So no copy is made
- nine_k 2y agoNor in Python. Nor in C++20's std::views. The very point of iterators us to proceed by one element, avoiding intermediate copies of collections. One case where the for-loop would be much more efficient is a simple transformation like incrementing each element of an array, or adding two arrays element-wise. A serious C compiler could unroll such a loop into several vector instructions which work on several elements at once. Maybe even LLVM can recognize something like that in Go code.
- margalabargala 2y agoThe original version relied on no fewer than five different imported functions from a package that, if you're not already familiar with them, you would have to go read to see exactly what they do. The updated version has just one such function. A function gets less readable as you have to go context switch to more and more different functions to understand what it's doing. Yes, the `slices` functions are named such that you can make a decent educated guess as to what they're doing, but if there's a problem you're trying to debug that doesn't save you from having to dive into each one.
- deleted 2y ago[deleted]
- scubbo 2y ago> A function gets less readable as you have to go context switch to more and more different functions to understand what it's doing. This is true. A function also gets less readable every time you have to mentally translate from low-level implementation code, to a high-/human-level description of "what is this actually doing?" (e.g. `val := a[len(a)-idx-1]` - "ohhh, ok, that's iterating in reverse"). Extracting out common patterns to methods with well-known names like `Filter` or `Map` or `ForEach` short-circuits that repeated parsing. > that doesn't save you from having to dive into each one. 99% of the time, it really does. If you can't trust something as basic and well-defined as `Map` to do what you expect it to do, you've chosen a poor library on which to take a dependency.
- margalabargala 2y ago> 99% of the time, it really does. If you can't trust something as basic and well-defined as `Map` to do what you expect it to do, you've chosen a poor library on which to take a dependency. The original comment said: it = slices.Map(it) I understand Map taking an array and a function, as in the article. I don't understand Map taking simply an array. Then I went to look it up, and turns out that slices.Map does not in fact exist. https://pkg.go.dev/slices https://pkg.go.dev/slices Personally I feel like this exchange is a vivid illustration of exactly my original point.
- coin 2y agoGo programming style is like programming in 2005 before functional programming because widespread (Google Guava, Clojure). If you showed functional programming to programmers in 2005 many of them would find it difficult to read.
- parhamn 2y agoI think the killer case for this is not hiding the computational complexity of mapping/reversing/etc. I write a lot of typescript and golang and I often notice in typescript my chains end up iterating the same array N times when it couldbe been done in one smarter iteration. I don't think I care though -- both styles are way more than performant enough. You can be more careful about it in cases it is not.
- tsimionescu 2y agoIterating an array 10 times doing 1 operation on each element each time is equivalent to iterating it 1 time while doing 10 operations on each element.
- parhamn 2y agoThis isn't true outside the theoretical best case. Definitely not true in most interpreted languages. Consider the performance difference of .reverse() (in-place or copy) vs using a descending for loop. Generally with interpreter chaining there are more "hidden" allocations/operations.
- tsimionescu 2y agoYou're comparing apples and oranges. Of course modifying an array or copying it takes way more time than looping. The comparison was not about that, it's about one loop with 10 instructions VS 10 loops each with a single instruction, for the exact same instructions.
- thrww120956 2y agoThere's no way to do this (a single instruction loop vs 10 loops with a single instruction) in JS or Golang.
- tsimionescu 2y agoI don't mean assembly-level instructions, just general instructions in the language: for i := range arr { foo1(arr[i]) ... foo10(arr[i]) } Vs for i := range arr { foo1(arr[i]) } ... for i := range arr { foo10(arr[i]) }
- everybodyknows 2y ago> it = slices.Map(it) There is no such function as "slices.Map()". https://pkg.go.dev/slices@go1.23.2 https://pkg.go.dev/slices@go1.23.2
- cyberax 2y ago> I don't judge the Go enjoyers, but I prefer writing TypeScript to Go which says it all. The functional style is fine for simple filter/map algorithms, but once you get into zipping multiple iterators together, classic for-loops are often easier. My company's hiring coding task had this "trap", at one point you needed to write an algorithm to merge consecutive "empty" cells in a data structure that is used to represent a simple table. This is dead easy with a regular for-loop, but writing it in a "functional" style could easily become extremely hairy and/or have O(N^2) complexity.
- scubbo 2y agoNit - I think you've missed the `func` argument in the `slices.Map` line. (But - yes, bravo, correct, you're right and you should say it)
- kubb 2y agoI did, but I believe some people understood what I meant anyway.
- beantaxi 2y agoThey might indeed. Or a := []int{1,2,3,4} it := slices.All(a) itRev := slices.Reverse(it) itMap := slices.Map(it) itFilter := slices.Filter(it, func(i int) bool { return i % 2 == 0 }) slices.ForEach(it, func(i int) { fmt.Println(i) }) No fun to write. Is it better to read? Yes, if you want to know exactly what's going on. Not really, if you are skimming through a lot of code, you're used to cruising through chains of func calls, and the above feels like speed bumps.