I was just working through some code to check for the intersection of two lists and use the result to iterate though. Creating new slices to put values in with loop in loops is not a productive use of my time.
I don't really want generics personally, I would just like some magic methods on top of the existing slice/maps in Go. They already are magic in the way they work so why not provide those few helper methods and solve a massive pain point I have with Go.
Faced with these 2 wonderful options, most people simply decide to copy or write again the same piece of code...
interface{} could be avoided by having an interface that reflects the behavior you actually need from the types involved in the algorithm. If you need to only test for equality, you could have an algorithm that works on []Eq. That is one of the downsides to Go, unlike Haskell, interfaces are usually defined by the consumer, which makes negotiating common interfaces like Ord and Eq difficult.
It's also a good way to make bugs proliferate throughout your code base. IIRC bug count correlates well with line count regardless of the language(!).
The idea of []Eq sounds nice, but again, the problem is that I can't pass an []X to a function that wants an []Eq, I need to build a new array and copy all my elements over. This is correct from the type-theory point of view (as slices are not covariant) , but of course Go doesn't offer anything else that would be more user friendly and still correct, such as a covariant read-only slice that we could use instead.
In 2019, having a generic implementation of the algorithm is also "a good way to make it apparent what the code is actually going to do and the associated cost of the solution" in most languages.
On top of that, it helps you have less code (easier to check, code is a liability), skip manual text-template-generated code pre-process steps, let's you code what you know you want without a context switch to copy something that you've already written for another type, and stop bugs stemming from e.g. changing things in one implementation of the same algorithm and not another (because you're forced to have 10 implementations for different types).
And copying has been an anti-pattern since the times of Algol. Except if it's replaced by the "wrong abstraction" prematurely. But nobody ever called having a generic version of an algorithm "the wrong abstraction".
Contrast that with C++ where all containers are implemented in C++ without cheating.
Go is full of special blessed functions and ad-hoc handling because of the lack of generics.
>If you need to merge two lists you can just write a method that does that. Publish it as a package and reuse it.
Only it wont work in any other type of list contents (or it will revert to interface{} and lose type safety and speed). Which is the parent's point.
Then write it in assembly and damn all the abstractions. /S
It’s madness. When writing application software in any language, you’re already umpteen levels of abstraction away from what’s really happening under the hood. You call a library that calls a library that calls another library over FFI that itself dynamically links to a function written in another language that’s all compiled down to object code that runs in little tiny timeslices chosen by a scheduler in an operating system that abstracts away the hardware running on a CPU that actually rewrites instructions using microcode to actually run on an internal RISC core but one more level of abstraction on top of all this is apparently where everything will go wrong.
Go focuses on code, not data and concrete solutions, not generic ones. It’s a language designed for the 90%. If that doesnt fit your requirements, it’s ok to use C++, Haskell, or Java, or some other equally good language. But Go is really, really good when you give it a fair chance.
They can also be accomplished through constructs Brainfuck already has. Actually any Turing complete language will do.
>Map filter and reduce are generic tools that can be accomplished through constructs Go already has. The point of Go is that most applications do not need a high level of abstraction, they are bounded problems.
map, filter and reduce are not "high levels of abstraction" any more than for or channels are.
>Therefore framing problems in terms of generic data transformations facilitated by mapping and filtering is drawing attention away from the concrete solution and making the code less clear.
The "concrete solution" being copy pasting code with a different type to filter a list for your types, and using a for loop instead of filter or map? Sounds more like Stockholm Syndrome.
Reduce is a bit different though because the closest equivalent on the cli is awk with a BEGIN and END block, so it's a bit more high-level. But there is still value in it.
The thing is most people who complain about the lack of those functions are not looking for fancy transformations: in most cases you want something as simple as "extract a field in each struct" or "keep if this field has this value". There even is sort.Search in the stdlib that has something in the same vein; surely something like
filter.Filter(n int, f func(I int) bool) []int
could be useful?This is not about Java, this is about a multitude of languages having these features and Go not having this is a valid criticism.
I'm not saying you're wrong that there might be good reasons for it to be focused on different things, but I didn't read OP as "meh this is not Java" at all.
I was talking about streams in Java 8, so ok - 2014 is 5 years ago.
Though ML was invented in 1973, so the idea of map/fold is pretty old.
The generalization into monoids/monads/functors didn’t happen until the early 90’s.
If we want to talk about the JVM, Scala was created 15 years ago and had those features.
I respect K&R, Rob Pike, C, and Unix a lot, but you have to understand that even when they were in their prime, they were considered a backwards. This can be traced toward the New Jersey style vs MIT style [http://dreamsongs.com/WorseIsBetter.html]
Not surprisingly, the New Jersey style originated at a company, Bell Labs, and while MIT style, not surprisingly, originated at... MIT.
But at this point, Go just feels antiquated, designed by a guy who strikes me as a “get off my lawn” kind of person.
Personally having had to transition from Scala to Go for one project at my old company, it felt like I was programming “blind, deaf, and dumb.”
Things I could do in 2 lines of Scala (though those lines were admittedly dense), would literally take hundreds of lines of Go. Sure, maybe it would have taken even more lines of C, but not much.
Go is probably just a tad more expressive than C with boehm GC. And frankly, that’s unexcusable for a language created in the 00’s, much less 2009.
Only if those make sense. "The philosophy, conventions, and style of the language" could very well be BS.
Take Java for example: the mid-2000s J2EE code with the endless abstractions, XML everywhere, patterns galore, etc, was accepted and used as the default Java "idiomatic style", but it was understood later to be cargo cult BS.
Similarly, Java didn't have Generics either pre 1.5 iirc. One could say like you now, writing without generics is part of the "philosophy, conventions, and style of the language".
But it wasn't. It was just a missing feature. Now that Java added it, nobody feels it was better before, and nobody feels like they're sacrificing some part of the "style of the language" for using it.
So, when so many engineers with lots of experience say that, for instance, no-generics is a problem with Go, it's worth considering that may be it really is a problem.
I wonder what you will say if Golang introduces generics in a future release.
Beware of evaporative cooling. All the people who can't put up with a lack of generics will wander off, and you'll only be left with people who are okay with the status quo. That doesn't mean you don't have a problem.
The Go core team has to decide if there's a way to add them to the language in a "Go way." If they can't they won't be added to Go at all, despite what other people want or don't want.
"People were right in a wrong way"
[^1]: Rust and Haskell pay for their marginal increase in robustness with a massive decrease in productivity.
https://play.golang.org/p/kbmrIvoQBXs
If you want true streams and don't mind the performance hit, you can certainly design a library to implement those semantics on top of Go channel types.
Not that I'm saying that Go should not have functional features - it does, but it also forces you to think more deeply on things.
Ultimately I concluded that map/filter/reduce really need to be paired with a concise lambda syntax in order to be ergonomic. Writing out a full func() literal doesn't feel much cleaner than just writing a traditional for loop. However, adding a new lambda syntax would be a much larger undertaking than I can manage right now.
The other thing I concluded is that you can highly optimize map/filter/reduce chains if they're first-class citizens of the language, rather than living in a userspace library. I was able to compile such chains into for loops that are more efficient than what a novice might write, and no worse than what an expert would write. The only way to improve would be to inline more aggressively, i.e. inline "x += 2" instead of calling out to "addTwo()" on each iteration of the loop.
I think there's an unfilled niche in programming languages that's basically "Go plus some FP niceties." I love Go's simplicity, tooling, and performance, but writing endless for loops and error-handling blocks gets tiring and can obscure the operation you're actually trying to achieve. At the same time, jumping to full-blown generics, macros, etc. like Rust introduces too much "magic," hurts readability, and bloats compile times.
Nim?
Haskell and lisp are like that for sure, but more blue collar FP languages like erlang ate "basically FP so that you get memory safety and easy multithreading".
I don't know what outsiders think about us, but 90% of the value derives from the simple things : constructing simple datatypes easily, a world-class type checker and some great libs that require little knowledge to be used, like Servant or Aeson.
Sure, there are fancier things if you want to go beyond that, but you can be productive with the bare minimum of Haskell, as Elm shows.