13 ms·
I like generics for collections but that is about it. I've seen "gifted" programmers turn everything into generic classes and functions which then makes it very
by rb808 6y ago
I like generics for collections but that is about it. I've seen "gifted" programmers turn everything into generic classes and functions which then makes it very difficult for anyone else to figure out what is going on.
One reason I like go is it doesn't have all the bells and whistles that give too many choices.
- jerf 6y agoI expect after a flurry of initial excitement, the community will settle on some standards about what it is and is not good for that will tend to resemble "Go 1.0 + a few things" moreso than "A complete rewrite of everything ever done for Go to be in some new 'generic' style".
- mixedCase 6y ago> which then makes it very difficult for anyone else to figure out what is going on Or we can learn to read them. Just treat types like a first class value. You either assign names to types like you do to values, or you can assign a name to a function that returns a type, this being generics.
- systemvoltage 6y ago> or we can learn to read them That's an awful way to think about hard to read code. I could produce the most unreadable one liners you've ever seen in your life. We should condemn that and not blame it on others to "learn how to read".
- ragebol 6y agoYou write code for an audience. In that audience, sit yourself in your current state, yourself a year+ from now, your colleagues (you know their level) and the compiler. With bad luck, your current self i a state pulling your hair out to debug. Think about the audience when you code.
- gowld 6y agoI assume you only program in readable languages like COBOL and AppleScript.
- mixedCase 6y ago> That's an awful way to think about hard to read code Most of the time I hear about "hard to read code" is "pattern I don't currently have a mental model for". We didn't move on from COBOL by letting that be a deterrant.
- systemvoltage 6y agoFair, I've actually seen both types of situations. I only complain after having some domain knowledge of the project and the language/tools. After sufficient understanding, I will make sure that the code that gets merged into master is highly readable. Simple > complicated. Always. Don't be ashamed to write simple code.
- michaelcampbell 6y agoAh, blub. It will never leave us.
- gokhan 6y agoWhat's wrong with this? class Result<T> { bool IsSuccess {get; set;} string Message {get; set;} T Data {get; set;} } On many occasions, I like using result types for defining a standard response for calls. It's typed and success / fail can be handled as a cross-cutting concern.
- londons_explore 6y agoI feel like such a class should either be part of the language, and part of language idioms etc, or it shouldn't be used.
- tomjen3 6y agoCan you articulate why? it seems to me that 'feel' should not be part of the discussion.
- deleted 6y ago[deleted]
- juststeve 6y agoNot GP but: It may frustrate coworkers who need to edit the code. It adds another dependency into your workflow.
- lenish 6y agoNot GP, but I've sometimes found libraries implementing similar concepts differently causing issues. E.g. libraryA.Result struct { Err error Data SomeDataType } libraryB.Result struct { err string Data SomeDataType } func (r libraryB.Result) Error() string { return r.err } Now you have two different implementations of the same fundamental idea, but they each require different handling. In Go, where many things simply return an error type in addition to whatever value(s), you would now have three different approaches to error handling to deal with as opposed to just whatever the language specified as the best practice.
- libria 6y agoAt this point I'd like to summon to go-generics defense all the PHP and Javascript developers who assert unwaveringly "Bad language design doesn't cause bad code; bad programmers cause bad code."
- draw_down 6y agoBoy. You guys always manage to find interesting new ways to hold PHP/JS devs in low regard. I will give you all that.
- DaiPlusPlus 6y agoCounterpoint: languages (and libraries, and frameworks, and platforms) so well-designed that they introduce a "pit of success"[1] such that bad programmers naturally write better code than they would have done otherwise. For example, what if PHP could somehow detect string-concatenation in SQL queries and instantly `die()` with a beginner-friendly error message explaining to use query parameterisation from the very beginning: tens of billions of dollars of PHP SQL injection vulnerabilities simply never would have happened - and people who were already writing database queries with string-concatenation in VB and Java who gave PHP a try would then be forced to learn about the benefits of parameterisation and they'd then take that improved practice back to their VB and Java projects - a significant net worldwide improvement in code-quality! [1]: https://blog.codinghorror.com/falling-into-the-pit-of-success/ https://blog.codinghorror.com/falling-into-the-pit-of-succes... I've been writing in TypeScript for about 5 years now - and I'm in-love with its algebraic type system and whenever I switch back to C#/.NET projects it's made me push the limits of what we can do with .NET's type system just so I can have (or at least emulate as closely as possible) the features of TypeScript's type system. (As for generics - I've wondered "what if every method/function was "generic" insofar as any method's call-site could redefine that method's parameter types and return types? Of course then it comes down to the "structural vs. nominative typing" war... but I'd rather be fighting for a hybrid of the two rather than trying to work-around an poorly-expressive type system.
- qppo 6y agoHere's some generic code I wrote in Rust recently. I had two unrelated iterators/collections and I needed to flatten and sort them into a single buffer. struct Data { idx: usize // ... } struct Foo { stuff: Vec<Vec<Data>> } struct Bar { stuff: Vec<Data> } fn flatten_and_sort (foo:Foo, bar:Bar) -> Vec<Data> { let mut output = foo.stuff.into_iter() .flat_map(|v| v.into_iter()) .chain(bar.stuff.into_iter()) .collect::<Vec<_>>(); output.sort_by(|lhs, rhs| lhs.idx.cmp(&rhs.idx)); output } Now you could argue that this is just "generics for collections" but the way those iterator combinators are written make heavy use of generics/traits that aren't immediately applicable for collections. Those same combinator techniques can be applied to a whole host of other abstractions that allow for that kind of user code, but it's only possible if the type system empowers library authors to do it.
- saghm 6y agoYou can actually use the power of generics to get rid of the two inner calls to `into_iter()`: let mut output: Vec<_> = foo.stuff.into_iter() .flat_map(|v| v) .chain(bar.stuff) .collect();
- coolreader18 6y agoI believe you can just use `.flatten()` instead of `.flat_map(|v| v)`: let mut output: Vec<_> = foo.stuff.into_iter() .flatten() .chain(bar.stuff) .collect(); I might make it slightly more clear what the intent is by doing this: let mut output: Vec<_> = Iterator::chain(foo.stuff.into_iter().flatten(), bar.stuff) .collect(); But still basically the same.
- politician 6y agoYes, functional programming patterns are nice. It's possible to write that even more concisely in JavaScript.
- ragebol 6y agoIf you don't understand someone else's code, you can either tell them they stuff is too complicated or learn and understand better. There can be a middle ground of course.
- logicslave 6y agoMost of the time if code is hard to understand its bad code. Just because someone writes complex code that uses all the abstractions, doesnt mean its good. Usually it means the opposite
- phamilton 6y agoI'd like generics for concurrency constructs. Obvious ones like Mutex<T> but generics are necessary for a bunch of other constructs like QueueConsumer<T> where I just provide a function from T -> error and it will handle all the concurrent consumption implementation for me. And yes, that's almost just a chan T except for the timeouts and error handling and concurrency level, etc.
- jdxcode 6y agoconsidering the vast majority of programming involves loops I don't see "just for collections" as some minor thing—it's most of what I do
- simias 6y agoI've certainly encountered codebases that abused inheritance and templates/generics to the point of obfuscation but you can abuse anything really. Besides in my experience the worst offenders where in C++ where the meta-programming is effectively duck-typed. Trait-based generics like in Rust go a long way into making generic code readable since you're always aware of what meta-type you're working with exactly. I definitely don't use generics if they can be avoided, and I think preemptive use of genericity "just in case" can lead to the situation you describe. If I'm not sure I'll really need generics I just start writing my code without them and refactor later on if I find that I actually need them. But even if you only really care about generics for collections, that's still a massive use case. There's a wealth of custom and optimized collections implemented in third party crates in Rust. Generics make these third-party collections as easy and clean to use as first party ones (which are usually themselves implemented in pure Rust, with hardly any compiler magic). Being easily able to implement a generic Atomic type, a generic Mutex type etc... without compiler magic is pretty damn useful IMO.
- jasonhansel 6y agoI can think of a few other potential use cases in Go. Some ideas: - Promises - Mutexes like Rust's Mutex<T> (would be much nicer than sync.Mutex) - swap functions, like swap(pointer to T, pointer to T) - combinators and other higher-order functions
- deleted 6y ago[deleted]
- gowld 6y agoMathematically, almost everything generic can be viewed as a collection.
- whateveracct 6y agoFunctions ;) s -> (s, a) is generic and a Functor (mappable - often conflated with a collection) but it's no collection!
- catalogia 6y agoI agree, those grapes are probably sour anyway...
- fendy3002 6y agoFor java / c#, in my experience, I've done that mistake because in both language the class declaration is very verbose. Then using generic is the only way to solve a problem which can only be solved by dynamic typing / variables. In typescript I don't need generic too much / too complex, because the typing definition is more lax, and we can use dynamic in the very complex scenario. I don't know which approach go is taking.
- xscott 6y ago> I like generics for collections but that is about it. What about algorithms (sorts, folds, etc) on those containers? I write a lot of numerical code. It sucks to do multiple maintenance for functions that work on arrays of floats, doubles, complex floats, and complex doubles. Templates/Generics are a huge win for me here. Some functions work nicely on integer types too.
- marcus_holmes 6y agoI think this is probably the single best use case for Generics for Go - numerical operations on the range of number types.
- dathinab 6y agoHonestly as long as you learn when to use generics and when to not use them there are a lot of very useful ways to encode state/invariant into the type system. But I also have seen the problem with overuse of generics and other "advanced" type system features first hand (in libraries but also done by myself before I knew better).
- Forge36 6y agoI've done this to one of my pet projects (thankfully unreleased). It just makes debugging/editing on the fly more difficult. I'd love to unwind the mess. But that'll take days fixing I caused in minutes! It's a big foot gun.
- neilparikh 6y agoWhat about genetics for phantom types? Ex. class ID<T> { int id; } The idea is that an ID is just an int under the hood, but ID<User> and ID<Post> are different types so you can’t accidentally pass in a user id where a post is is expected. Now, this is just a simple example that probably won’t catch too many bugs, but you can do more useful things like have a phantom parameter to represent if the data is sanitized, and then make sure that only sanitized strings are displayed.
- metrognome 6y agoJust to note, for this specific example, Go supports this with type definitions: // UserID and PostID are distinct types type UserID int type PostID int
- neilparikh 6y agoOh neat! Most languages make it a little bit verbose to create these kinds of wrapper types for type safety (with zero overhead), so it's nice that Go has that. I think the generic approach is a little bit better because of the flexibility, but this approach is still better than not having it at all.
- njs12345 6y agoThis isn't quite the same, because it's just an alias - you can pass a UserID to a function accepting a PostID: https://play.golang.org/p/nSOgcJs_66y https://play.golang.org/p/nSOgcJs_66y It still provides a documentation benefit of course. EDIT: Whoops, yes, as lentil points out, they are indeed distinct types not aliases. So it does provide the benefit of the Rust solution.
- lentil 6y agoNo, it's not an alias, they are distinct types. You can't use the types interchangeably (unless you cast them). Your playground example didn't try to pass a UserID to a function accepting a PostID, but if you do that, you'll see the error: https://play.golang.org/p/vyiJ_sLzy4O https://play.golang.org/p/vyiJ_sLzy4O
- gonzo41 6y agoYeah i actually think just having a built in genecic linked list, tree and a few other abstract data types would solve 90% of everyones problems. Part of the good thing about go is you solve problem more then you create them.
- ookdatnog 6y agoThere is an underappreciated advantage to using generics in function signatures: they inform you about exactly which properties of your type a function is going to ignore (this is called parametricity: https://en.wikipedia.org/wiki/Parametricity https://en.wikipedia.org/wiki/Parametricity) For instance, if you have a function `f : Vec<a> -> SomeType`, the fact that `a` is a type variable and not a concrete type gives you a lot of information about `f` for free: namely that it will not use any properties of the type `a`, it cannot inspect values of that type at all. So essentially you already know, without even glancing at the implementation, that `f` can only inspect the structure of the vector, not its contents.
- steveklabnik 6y agoNot all generics are parametric.
- ookdatnog 6y agoAgreed. From a quick skim of the Go generics proposal I get the impression that they are in fact aiming for parametric generics though (in fact they use the term "parametric polymorphism" in the background section).
- throwawaygo 6y agoThe go team's attempt at involving everyone in the priorities of the language has meant they lost focus on the wisdom of the original design. I spent 10 years writing go and I'm now expecting to have to maintain garbage go2 code as punishment for my experience. I wish they focused on making the language better at what it does, instead of making it look like other languages.
- throwawaygo 6y agoThat said the go team is incredibly talented and deserve a lot of kudos for moving much of the web programming discussion into a simpler understanding of concurrency and type safety. Nodejs and go came out at the same time and node is still a concurrency strategy salad.
- slaymaker1907 6y agoI like generics but I find that it is often best to start out writing a version which is not generic (i.e. explicitly only support usize or something) then make it generic after that version is written. As a side benefit, I find that this forces me to really think about if it should actually be generic or not. One time I was writing a small Datalog engine in Rust and was initially going to make it take in generic atoms. However, I ended up deciding after going through the above process that I could just use u64 identities and just store a one to one map from the actual atoms to u64 and keep the implementation simpler. I agree with the sentiment that it is very easy to overuse genetics though there are scenarios where they are very useful.
- shadowgovt 6y agoAnd that's among the reasons it's been left out of Go. Go design was guided by experience working on large software systems; the risk with making a language too flexible is that developers begin building domain-specific metalanguages inside the language, and before you know it your monolingual codebase becomes a sliced-up fiefdom of various pieces with mutually-incompatible metasyntax that defeats the primary advantage of using one language: developers being able to transition from one piece of the software to another without in-depth retraining. For enterprise-level programming (which is the environment Go grew up in), a language that's too flexible is a hindrance, because you can always pay for more eng-hours, but you can't necessarily afford smarter programmers.