Reflection APIs seem to be pretty messy and slow in every runtime I've ever used, perhaps because the idea of optimizing them might encourage more use. The C# reflection APIs also allocate a lot.
A thing you can ding Go for is that you can find yourself relying on `reflect` (under the hood) more than you expect, because it's how you do things like read struct tags for things like JSON.
But that's not what the problem was here; the product they were building was using `reflect` in anger. They were relying on something that did magic, pulling a rabbit out of its hat to automatically compare protobuf thingies. They used it on a hot path. The room quickly filled with rabbit corpses. I guess you can blame Go for the existence of those kinds of libraries, but most perf-sensitive devs know that they're a risk.
Reflection is also typically needed for anything that needs to be generic over types. For example, if you want to write a function that can traverse or transform a map or slice, where the actual types aren't known at compile time. We have a lot of this in our Go code at the company I work for. I'm really looking forward to generics, which will help us rip out a ton of reflect calls.
That kind of code is generally non-idiomatic in Go. An experienced Go programmer looks at something that is generic over types and does something interesting and instinctively asks "what gives, where are the dead rabbits?".
I'm less excited about generics. There's a cognitive cost to them, and the constraint current Go has against writing type-generic code is often very useful, the same way a word count limit is useful when writing a column. It changes the way you write, and often for the better.
This argument is getting a little tiresome though, isn't it? It isn't simply enough to call something "non-idiomatic" to gloss over a deficiency. There's a cognitive cost to all language features, but most other general purpose statically typed programming languages seem to have come to the conclusion that the benefit outweighs the cost for some form of generics.
I am by no means a Go basher, it is one of my favorite languages. But I eagerly await generics.
I could have written this more clearly. The fact that things that are generic over types are non-idiomatic today in Go has nothing to do with whether the upcoming generics feature is good or bad. They're unrelated arguments.
The latter argument is subjective and you might easily disagree. The former argument, about experienced Go programmers being wary when an API is generic over types, is pretty close to an objective fact; it is a true statement about conventional Go code.
That's a fair point. Knowing not to try to write generic code (since you don't have the tools) is the sign of an experienced Go programmer.
That being said, I'm curious how much kubernetes (a large, famous, Go codebase) still has code that does this. I used to read that it used a ton of interface{} and type assertion, but maybe that narrative is out of date (or never really true). I was never too familiar with the codebase myself.
I’m so conflicted on that point. I’ve been writing a high performance CRDT in rust for the last few months, and I’m leaning heavily on generics. For example, one of my types is a special b-tree for RLE data. (So each entry is a simple range of values). The b-tree is used in about 3-4 different contexts, each time with a different type parameter depending on what I need. Without genetics I’d need to either duplicate my code or do something simpler (and slower). I can imagine the same library in javascript with dynamic types and I think the result would be easier to read. But the resulting executable would run much slower, and the code would be way more error prone. (I couldn’t lean on the compiler to find bugs. Even TS wouldn’t be rich enough.)
Generics definitely make code harder to write and understand. But they can also be load bearing - for compile time error checking, specialisation and optimization. I’m not convinced it’s worth giving that up.
If we can be at a place where reasonable people can disagree about generics, I'm super happy, and think we've moved the discourse forward. There are things I like about generics, particularly in Rust (I've had the displeasure of dealing with them in C++, too). They're just not an unalloyed good thing.
Before writing Clojure, Rich Hickey wrote FOIL[1], which used sockets to communicate between common lisp and the JVM (or CLR). When asked about making it in-process, Rich observed that the reflection overhead on the JVM was often as large, or larger, than the serialization overhead, so the gains to be had were limited.
From what I recall, the Java team copped to the intentionally slow accusation, but that started to change when they decided to embrace the notion of other languages besides Java running on the JVM. Unfortunately that would have been shortly after Clojure was born. It took a few releases for them to really improve that situation, and that was still shortly before they started doing faster releases.
The usual C# reflection APIs that devs turn to allocate a lot, but there are ways to make them almost performant by (re)using delegates and expressions. There are a number of good libraries to use reflection faster, as well.
IIRC, dynamically compiled expression trees in C# have the overhead of a single virtual call (on the resulting delegate) when executing - and cover all object factory and member access scenarios. But if you need to discover metadata, you still have to resort to Reflection APIs.
The main corner case that still causes me problems is that if you want to construct a delegate at runtime, this often forces you to go through the reflection APIs to actually grab the method even if you know its full signature, etc. My current project has a JIT compiler for scripts that has this problem (I ended up finding a workaround involving getting LINQ to generate method tokens in an assembly, but .NET Core / .NET 5 deprecated LINQ compilation...)
I hadn't heard people use messy to refer to garbage cration before!
To continue with Python, yes, you might get a new container (dict) allocated like in the above case to hold the already existing interned attribute name strings. It's still quite light since the object representing the information already exist and are used under the hood in the dynamic typing machinery.