Comment by fauigerzigerk
14 hours ago
It's hard to avoid, because (naming aside) the cognitive load is caused by higher order functions, which are hard avoid without causing massive code duplication.
I understand the desire to keep things concrete and avoid high level abstractions, but it's a decision not to automate stuff that can easily be automated. It runs counter to the basic instincts and purpose of our field/industry. That's why it never sticks.
Honestly, I’ve written some applications that, on paper, should be the perfect candidate for generics. And yet I can still count on one hand the number of times generics have saved me from massive code duplication.
Most of the time generics might be useful, I’ve ended up needing reflection too anyway. And at that point, I’m really no better off for generics.
Generics are supremely useful for containers. They are kind of one trick ponies in that sense in application code. Similar to reflection, I’d say, which is utilized for serialization 99% of the time.
The thing is that one use case is so essential, so foundational, that we really can’t just skip it. You need generic containers, for ergonomics and performance. I mean, compare C qsort to C++ std::sort.
The languages that “get around” generics, like PHP, include god containers in the runtime. The language I’m designing is also that way, I’d like to avoid generics preferably forever.
But there’s a tradeoff there. God containers are very flexible, and we’ve seen the ramifications of untyped PHP arrays.
I understand that this is true for a lot of application code. It's not true for library authors though, and every language needs libraries.
I’ve written a lot of libraries too.
The problem is generics only solve a very small part of the equation: compile time checks for composite types. But to use composite types in anything non-trivial in Go, you then need reflection. Which is slow. And if you then need reflection, you’re already passing interface types anyway plus you’re back to having to handle type-handling errors in the runtime.
So if you’re writing a library that’s expected to have any kind of performance, you’re back to code duplication and having a DoSomethingType() function signatures again.
Or you stick with reflection and take that performance hit PLUS the risk of compile time constraints being runtime errors; which is the a lose-lose scenario. And let’s also not forget that reflection can be just as verbose as code duplication, and harder to get right too.
Don’t get me wrong, I’m glad we have generics. But people on HN massively overstate the value of them in a AOT non-dynamic, strictly typed language like Go.
I guess you could argue that Go has other shortcomings that directly result in generics having limited value. But then you’re basically just arguing that you prefer coding in a different language paradigm, and at that point, you’re much better off using that other paradigm instead of complaining that Go isn’t JavaScript or Haskell.
3 replies →
Lisp manages it. Even if you do use type annotations.
No. This cognitive load is conceptual. You can't avoid it by using slightly different syntax.
I think the problem is that the Box / Map / any stuff and all the explicit declarations that Go requires makes it harder.
In Haskell as well, you can let the compiler infer a lot of things but that doesn’t appear to be the case with this example.
I’d want the compiler to infer things, but that - I think - is at odds with Go desiring a fast compiler, which I also understand.
1 reply →