6 ms·
Can somebody post an example of when you would use generics to solve a problem in the real world, and how that would be implemented in Go instead?
by MuffinFlavored 7y ago
Can somebody post an example of when you would use generics to solve a problem in the real world, and how that would be implemented in Go instead?
- nine_k 7y agoYou can solve any problem in a language without generics, such as Go, C, Fortran-IV, or any other Turing-complete language, including the Turing machine "assembly". The difference is in speed of producing such code, amount of bugs introduced, ability to understand, modify, and evolve the code, etc.
- takeda 7y agoDon't know Fortran, but while C doesn't have generics you're getting similar behavior due to macros (for example min(), max() are macros) and weak typing. So the need of having generics there is much weaker.
- fwip 7y agoThere are a number of tools (and the "go generate" construct) designed to support code generation. If you just want macro-level behavior, that's easy to do.
- takeda 7y agoYes, and that's what I'm currently doing, but it is essentially just automating writing the same code multiple times. It still has issues, like having to remember to regenerate the code on change, or making harder to use IDE features to refactor. There are also some limitations it has.
- nine_k 7y ago> automating writing the same code multiple times What would you call a language that forces you to write / use a preprocessor to introduce higher-level features? Low-level. More specifically, low-level for the domain where you're working. C is admittedly low-level because it had to be minimal even for 1970 and close to the metal. Other languages usually enjoy less drastic design constraints.
- pjmlp 7y agoYep, that is why some were already using PL/I and Algol dialects almost 10 years before C came to be, alongside Fortran, Cobol and Lisp.
- pjmlp 7y agoJust like Borland C++ 2.0 for MS-DOS did, before templates were started to be designed. I let you research when it was released.
- pjmlp 7y agoEven C does have generics now (_Generic) even if quite limited.
- takeda 7y agoNeat, I had no idea.
- killbot5000 7y agoThe simplest example I can think of is: Map(slice, func) You can can implement this generically in Go using interface{} types and runtime type checking, but then you have runtime type checking failures. A java/c++-esque "generics" implementation would be able to type-check at compile time.
- ori_b 7y agoYou can implement it in Go using loops, and it's not significantly slower or more error prone.
- hauleth 7y agoUnless you want to implement that on top of your custom structure, like, for example, b-tree. Then you either need to write N different implementations for each separate tree or you need to use `interface{}`.
- ori_b 7y agoYes, custom containers are one place that generics are very useful. Go has the most useful containers built in. Most code I write doesn't need custom containers, even in languages with templates (I write far more C++ and Java than Go) -- so I find that I don't miss them much when I spend time in Gopherspace.
- NoodleIncident 7y agoThe most egregious missing container is "set", coming from Python (as many Go developers do). After debugging, reviewing, and writing at least 3 different local implementations of a set, I can't wait for generics so that it can be written once and then never looked at again.
- mappu 7y agoI'm slightly surprised you found three different set implementations - `map[T]struct{}` is the idiomatic one i see everywhere. I suppose it's not sorted or concurrent-safe?
- peterbraden 7y agoFor a Math.max function you would normally have generic comparables and implement on that. Go makes you implement max and min in all your codebases.
- takeda 7y agoSame with float, if you for some reason need to use float32, tough luck. Makes you wonder why even have types that aren't supported by the standard lib.
- mamcx 7y agoIn fact with Go is even more obvious. Go don't have inheritance! Traits is a way to solve it. But the problem is that in Go types are "too open". If you wanna constrain it more traits is not an elegant solution. Generics is better in this case. BTW: This is how is in Rust. Rust have traits but you fill the rest with generics. I made a mini-go database command liner that I call from rust (so I get access to drivers that are not yet available in rust). Is much more boilerplate from some stuff that in the rust side is just Value<T>. And type errors that Go have that rust don't. I think that Rust & Go sharing some(most?) design principles (about how model types) make easier to see what each side make harder...
- jerf 7y agoYou can prevent external packages from implementing your interfaces by adding unexported method names to your interface. Usually I'd call it an anti-pattern for interfaces, but I've done it a couple of times when there was literally no conceivable way for an external package to implement something conforming to a particular interface for various technical reasons.
- zenhack 7y agoYeah, most "good" uses of this technique are kindof abusing it to substitue for some other feature the language doesn't have, the most common of which I can think of is combining it with type switches to get a poor-man's sum types (what rust calls enums). You see this a lot in code working with ASTs, especially.
- rmilejczz 7y agoA lot of the time you’ll end up with an implementation for each discrete possibility, but that’s pretty crappy especially if you have more than 3 or 4 discrete types you need logic for (though not always bad, some go libs follow this pattern e.g. sort offers a method for each basic type). You could use the empty interface (which is essentially ‘any’), but that generally requires you to do casting or runtime type checking and that’s suboptimal in a statically typed, compiled language. Generally it makes more sense to describe an interface that can be satisfied by different types, and writing logic to handle that interface instead. Go may not have traditional generics but the interface pattern is certainly a kind of generic programming that can be used to achieve some powerful results. Because structs can satisfy many interfaces and interfaces can be satisfied with any kind of struct (if you want an in language example check out the io.Writer interface and the many functions that use it) you can end up with highly reusable code despite not having traditional generics.
- DocSavage 7y agoOne example is n-dimensional arrays where each element could be a byte, uint16, int16, ... , uint64, int64, float, or even some custom vector. In a language with generics, you could describe the nD array more simply and the operators work depending on what was passed. The implementation can also be shared across a number of similar element types. In Go, though, you’d need to use interfaces and at some point you’d have to make tradeoffs about speed, simplicity of the implementation (e.g. hand-coding each element implementation) vs use of interface function calls to get properties, etc. One example is to look at how Go handles the Image package vs other languages with generics. https://golang.org/pkg/image/ https://golang.org/pkg/image/
- leshow 7y agoliterally any data structure that stores different types similarly. hashmaps, linkedlists, vectors, binary trees (assuming you can bound on some ordering function), etc, then all the operations you expect to work on those data structures. You could also define mapping operations that work on the containing structure and don't care much about the specific type of the contents. There's a million and one uses for generics (polymorphism). In Go, I believe you need to give up type safety to write these implementations, by using Interface{}
- atoav 7y agoIn my experience Generics become much more useful, when your typesystem is strict. I use them quite often in Rust, especially when writing functions that should take many different types as inputs/outputs. You can get away without them in many cases, but if you are working on stuff that you want to reuse, generics are the way to go. In combination with Rust’s traits this is especially cool.
- tgv 7y agoI have functions that have to deal with hierarchical data objects, JSON like things. All collections are identically typed (although they have optional fields, different length arrays, etc.), but there is more than one type. In order to write simple code, I wanted to write one function that gets them from the database, one to update a single record, one to notify of changes, etc. So my top-level type to cover all of these collections is map[string]interface{}, which in TypeScript would be Map<string, any>. Then I ran unit tests, and sometimes a weird error cropped up. Turns out I made an error in an append: instead of concatenating two arrays, I added the second array as the last element of the first. That was a time consuming bummer. This problem could have been prevented by two approaches: copying every function for every type, or generics. Go is a nice language, which makes some tasks easy to implement, almost fool proof, but it still lacks in other areas.
- zenhack 7y agosuggest reading: https://blog.golang.org/why-generics https://blog.golang.org/why-generics
- otikik 7y agoNon-nullable types in are implemented using a combination of (powerful) enumeration types and generics. Enumeration types in rust can be the usual ones you find in other languages: enum UserStatus { Anonymous, Registered, Confirmed, } But their variants are not limited to names, they can be other things[1]: enum UserStatus { Anonymous, Registered{ email: Email, date: Date }, // a struct Confirmed(date), // a tuple } Enum types can also be made generic. For example the Date type could be a parameter in the example above: enum UserStatus<T> { Anonymous, Registered{ email: Email, date: T }, // a struct Confirmed(T), // a tuple } Which brings us to the Option type[2] in rust. It is an Enum with a generic type attribute, defined like this: enum Option<T> { Some(T), // a tuple with a single item of type T None, } In rust, when a function says it returns an `i32`, it will always return an `i32`. It never returns `i32 or null`. If you want not return an `i32` sometimes, you must specify exactly what else it can be. It is often convenient to return an Option<i32> instead. Then it can return `Some(i32)` or `None`. The difference with the "implicit null" from before is that the compiler will force you to "deconstruct the option safely", in compile time. You will never get a "Null Pointer Exception" in run time because of this. For me, this is huge, and one of the reasons I like Rust more than Go. [1]: https://doc.rust-lang.org/rust-by-example/custom_types/enum.html https://doc.rust-lang.org/rust-by-example/custom_types/enum.... [2]: https://doc.rust-lang.org/std/option/ https://doc.rust-lang.org/std/option/