3 ms·
I am not a Go user, but I've been following it from the beginning. What I've observed is that since one of its design principles is simplicity, it left out feat
by cicero 10y ago
I am not a Go user, but I've been following it from the beginning. What I've observed is that since one of its design principles is simplicity, it left out features that many people think are essential for a modern programming language. This results in a lot of criticism of the language which provokes a defensive reaction that might overstate their position.
The truth is that one does not need generics. I've worked for decades in languages that don't have generics. The real question is with current software capabilities and requirements, is the convenience provided by adding generics to Go worth the cost of the added complexity. People clearly disagree, and the language designers decided "no". That's not dogma; that's engineering.
- camus2 10y ago> The truth is that one does not need generics. Sure ,when you can use interface {} AKA void pointers everywhere, you certainly don't need generics /s You need generics if you want to avoid context.Context.Value(interface {})interface {} like API. You need generics if your goal is type safety AT COMPILE TIME. Obviously it's not one of the goals of Go /s ...
- dmit 10y agoHere is an illuminating (to me) post that shows how the Go approach to solving algorithmic problems might be different from other languages (in this case, a generic graph data type + Dijkstra's algorithm): https://groups.google.com/forum/#!msg/golang-nuts/Rj5t1h_ztxI/Fgs6HfLqMHAJ https://groups.google.com/forum/#!msg/golang-nuts/Rj5t1h_ztx... Sure, the final implementation still has a couple instances of interface{}, but they are much more innocuous than your example.
- randomdata 10y agoOut of curiosity, how would you avoid `Context.Value(interface{}) interface{}` with generics? The net/context package was designed to pass request lifetime context throughout your service infrastructure (think microservices), where the values provide metadata from your transport protocol. HTTP headers, if we want to think in terms of HTTP, although obviously there is no specific protocol dependency at this layer. The value may not even be assigned by a Go program, so it seems that runtime validation would be a necessity even with generics. A broken or malicious program upstream could easily inject something into the context that you weren't expecting, or simply not supply the value at all.
- comex 10y agoTo clarify, Contexts don't automatically get HTTP headers stuffed into them, right? Instead you mean that some layer of the Go program would insert data based on headers, and a later layer would take it back out? I'm not a Go user but that's what I gathered from some Googling. So, if you want to use types to make Context safer to use - and I'm not saying this is necessarily desirable, but assuming it is - you could start with something that works in the existing Go language: explicitly marking what kinds of objects can be keys rather than using interface{}. type Key interface { _dummy_key_method() } type Context interface { ... func Value(key Key) interface{} } func WithValue(parent Context, key Key, val interface{}) Context (Incidentally, I find it odd that WithValue is a freestanding function while Value is a method; I suppose this is to reduce the burden of reimplementing the Context interface for a new type, but is there any actual reason to want to do that?) This helps prevent you from accidentally using the wrong object as a key, a bug that might be tricky to catch if the key you meant to use is legitimately sometimes not present in the Context (in which case your code wouldn't error out due to nonpresence of the wrong key). It also prevents hacks like using strings as keys in lieu of declaring dedicated constants, which would create the risk of silent failure due to typos. (It seems this is already considered a bad practice, but such a signature would enforce it.) Once you do that, it's apparent how generics could further improve safety. Using imaginary Go syntax: type Key<T> interface { _dummy_key_method() } type Context interface { ... Value<T>(key Key<T>) *T } func WithValue<T>(parent Context, key Key<T>, val *T) Context Now you're safe from type mismatches. Actually, it seems that best practice is already for packages to provide type safety by defining wrapper functions, such as this from userip: func NewContext(ctx context.Context, userIP net.IP) context.Context func FromContext(ctx context.Context) (net.IP, bool) That works well enough, but generics save you the boilerplate; packages could just expose the keys as public constants while retaining type safety. Admittedly, those two functions are only a few lines to define, so there's not that much boilerplate to save, but it's something. Generics would also make it possible to write type-safe generic helper functions involving contexts, though tbh I can't think of many obvious ones you'd want. Maybe a function that panics if it can't find the key: func MustHaveValue<T>(ctx Context, key Key<T>) T ...though that would be better handled by a proper Option<T> type anyway. But that too would be enabled by generics! Okay, but there's still more that the compiler could guarantee. The value is now guaranteed to have the right type if it's there, but for all we know it could be missing. Indeed, this is probably a bigger footgun than types are in practice: it's easy to accidentally have some code path that bypasses the code responsible for setting a particular key. Unfortunately, this is a harder problem to solve even with generics, and in fact multiple Rust web frameworks have run into very similar issues: https://github.com/nickel-org/nickel.rs/issues/79 https://github.com/nickel-org/nickel.rs/issues/79 https://www.reddit.com/r/rust/comments/2cl7bw/web_frameworks_why_not_static_typing/ https://www.reddit.com/r/rust/comments/2cl7bw/web_frameworks... Arguably what you really want here is extensible records with a structural type system. For example, in Elm, you can have the expression { foo = "1", bar = "2" } and it'll be of type { foo : String, bar : String } But then, given any record type, you can add more stuff onto it (I think this works but haven't tested it): type alias HasFoo a = { a | foo : String } addFoo : a -> HasFoo a addFoo rec = { rec | foo = "1" } With a feature like that, your functions' context parameters, instead of having the plain Context type, could explicitly specify that they need a Context with a given key. Thanks to generics, you could call functions that add new keys without losing track of the keys already in the map; for example, with the above Elm code, if you called addFoo { bar = "2" } the result type would be { bar : String, foo : String } In fact, it is possible to simulate this in languages that lack native support for extensible records but do have sophisticated enough type systems - anything from C++ to Rust to Haskell. I think the Iron web framework for Rust does that now, though I only took a quick glance at the docs. I'm not sure exactly what this would look like in Go, both because the implementations tend to get pretty ugly/hacky, and because a hypothetical generics feature for Go might not add enough power to make this expressible. (Which, to be clear, isn't necessarily a bad thing; every bit of complexity has a price, and C++ templates in particular are notoriously awful to read and debug.)
- Manishearth 10y agoYou rarely have `interface{}`, you usually have concrete interfaces. These have the same amount of type safety at compile time for function inputs, there's just an additional runtime cost, which the Go folks are willing to pay. The fact that interface code often has runtime failures is usually due to a feature Go interfaces get you that many languages with generics don't have in the first place -- the ability to downcast to concrete types. Now, interfaces sadly do not have the same amount of type safety at compile time for defining new containers and you can't do things like return value polymorphism as well, but it's not that bad. It requires a different style of programming (the post from rsc linked in the sibling comment is particularly good at illustrating this), but it's not that bad. I say this as a Rust programmer (also a Go programmer, but primarily a Rust programmer) who absolutely loves generics.
- camus2 10y ago> You rarely have `interface{}`, you usually have concrete interfaces. you're assuming you know the kind of problems I'm trying to solve everyday. You don't. Don't patronize me.
- Manishearth 10y agoI was using a more generic "you", talking about most of the Go programs I've seen. By "you" I meant "Go programmers". Your situation may be different, but I'm describing what most Go programs (that I've seen) look like wrt their use of interface.
- civility 10y ago> The truth is that one does not need generics. I write a lot of algorithms that can work with float, double, complex float, and complex double. Without generics/templates (or a macro pre-processor), I have to cut and paste that code 4 times, or I have to sacrifice performance. Maybe I don't need templates, but I really really want them.