16 ms·
To be frank, I was for a long time on the camp that Generics is a much-needed feature of Go. Then, this post happened: https://news.ycombinator.com/item?id=1729
by rdsubhas 8y ago
To be frank, I was for a long time on the camp that Generics is a much-needed feature of Go. Then, this post happened: https://news.ycombinator.com/item?id=17294548 https://news.ycombinator.com/item?id=17294548. The author made a proof-of-concept language "fo" on top of go with generics support. I was immediately thrilled. Credit to the author, it was a great effort.
But then, after seeing the examples, e.g. https://github.com/albrow/fo/tree/master/examples/ https://github.com/albrow/fo/tree/master/examples/, I could see the picture of what the current codebase would become after its introduced. Lots of abstract classes such as "Processor", "Context", "Box", "Request" and so on, with no real meaning.
Few months back, I tried to raise a PR to docker. It was a big codebase and I was new to Golang, but I was up and writing code in an hour. Compared to a half-as-useful Java codebase where I have to carry a dictionary first to learn 200 new english words for complicated abstractions and spend a month to get "assimilated", this was absolute bliss. But yes, inside the codebase, having to write a copy-pasted function to filter an array was definitely annoying. One cannot have both I guess?
I believe Golang has two different kinds of productivity. Productivity within the project goes up quite a lot with generics. And then, everyone tends to immediately treat every problem as "let's create a mini language to solve this problem". The second type of productivity - which is getting more and more important - is across projects and codebases. And that gets thrown out of the window with generics.
I don't know whether generics is a good idea anymore. If there was an option to undo my vote in the survey...
- jadbox 8y agoAll I want is typesafe efficient higher order list operations like map, filter, reduce, flatmap, zip, takeUntil, etc. I don't care if Go puts them into the stdlib and only allows these operators to use special internal generics to operate. If Go had a robust higher order std library like this, I would stop asking for generics. Again, must be typesafe and zero overhead.
- bradfitz 8y agoOne of the main motivations for adding generics is that we could then put typesafe algorithms and data structures in the standard library.
- quotemstr 8y agoAnd make the standard library unmagical? One of the things I've always disliked about Go is how stdlib provides magic facilities unavailable to user code.
- icholy 8y agoWhat's magical about the stdlib?
- grey-area 8y agoMaybe they meant the language, which has some generic functions like append or delete - instead of those functions being attached to a data type or accepting any type, they only act on built-in types. I doubt any of that would change though given the stated goal of remaining compatible in most cases.
- SamWhited 8y agoThere are a handful of packages (time and some stuff in os for example) that are tied into the runtime and can't be rewritten outside the standard library. It's not a huge amount, and some of it can probably be fixed in the future.
- bradfitz 8y agoI agree, but there's not much, and it's usually for decent reasons: * the "time" package hooks into the Go scheduler. * the "reflect" package needs type information out of the runtime. * the "net" package hooks into the Go scheduler a bit. But way less than it used to. I think you might even be able to do this all yourself outside of std nowadays. What else? Our rough plan going forward is to actually move a bunch of the stdlib elsewhere but ship copies/caches of tagged versions with Go releases. So maybe "encoding/json" is really an alias for "golang.org/std/encoding/json" and Go 1.13.0 ships with version 1.13.0 of golang.org/std/encoding/json. But if you need a fix, feature, or optimization sooner than 6 months when 1.14.0 comes out, you can update earlier.
- 8y ago
- ori_b 8y agoThose are the things that I don't want to see in Go, honestly.
- pwm 8y agoI'm curious: why not?
- ori_b 8y agoGenerally, chaining things in the style that encourages that makes it harder to debug the code, harder to step through it in my head or on paper, and harder to compile to performant code. I'd rather see loops.
- bschwindHN 8y agoOn the other hand, you're less likely to need to debug those things because they've been implemented once, correctly. A manually-written filter, map, etc. will pretty much always have a higher probability of containing a bug. And in languages that have these features, I've never found it difficult to debug. What issues have you had in the past?
- ori_b 8y agoI've almost never had issues with manually written filters/maps -- at least, not with the iteration itself. On the other hand, having to untangle stack traces of closures of closures that have been passed through closures does slow me down when the function that's being mapped is buggy.
- bschwindHN 8y agoI guess I've never run into issues where a map or filter slowed me down for debugging, most languages will pretty easily show you where the error occurred. And if you're `map`ping and `filter`ing with pure functions, debugging is pretty simple because you just need the input that went into the buggy function. A good compiler will also optimize the closures into something you'd write by hand in Go. Overall I'm glad to see Go get these nice tools for more easily working with collections.
- gekkonier 8y agoSo you want a Lisp with other syntax, right?
- coldtea 8y agoYou say it like it's a bad thing.
- gekkonier 8y agoI was just curious, because those functionionality wished is in nearly every list processor, no mather what syntax used. And if not you macro that away, right?
- codr4 8y agoThat's about right. And gradual types, multi methods, generics and tight integration with C++: [0]. Dylan was too long winded for my taste, as are most modern attempts. [0] https://github.com/codr4life/snabl https://github.com/codr4life/snabl
- platinumrad 8y agoAt that point why not just add generics...
- nine_k 8y agoLisp tends to build abstractions, too, and it also does that via macros, which are even harder to mentally parse unless you have a habit. I'd say that to attain simplicity and clarity, you have to limit the use of abstraction. OTOH there's no way to avoid abstraction at all; programming is all about abstraction. If the means of abstraction are not powerful enough, copy-paste programming and boilerplate proliferate, making code less maintainable and harder to understand.
- platinumrad 8y agoIn my opinion, implementing these higher order functions with the help of one powerful abstraction (generics) is much simpler than having to deal with a bunch of special cased weaker abstractions (magic map, magic filter, magic reduce, magic...).
- krylon 8y agoI basically agree. I still think, while we're at it, making Generics a general feature of the language would probably be easier than adding all kinds of special cases. But yeah, if Go just added a generic map/filter mechanism (Python's list comprehensions (in turn inherited from Haskell) look very nice), that would cover about %75 of the use cases I want Generics for. Add a generic mechanism to use the range operator with user-defined types, and we're at about 95%.
- thiht 8y ago> Python's list comprehensions (in turn inherited from Haskell) look very nice Do they? I find them hard to read and hard to chain. I tend to prefer basic map, filter, reduce methods
- krylon 8y agoMmmh, that is a good point. I never used nested list comprehensions, to be honest, and I have not used Python in a couple of years. For non-nested cases, they are very nice, though. OTOH, I never used nested map/remove-if constructs in Lisp, because that, too, can get rather annoying to read.
- AndyMcConachie 8y agoI agree with this sentiment completely. Of course, if the only way to get these wonderful list operations is generics, then I guess I want generics, too.
- nemo1618 8y agoYou might be interested in my project: https://github.com/lukechampine/ply https://github.com/lukechampine/ply The idea is that when people say "generics", they usually just mean map/filter/reduce. So ply just bakes those into the language directly instead of adding new generic programming facilities.
- andybak 8y agoYou can't legislate against poor taste. Community norms help but there's always exceptions. It's interesting to consider Python which - generally - has strong community norms against excessive abstractions and too much magic. There are some notable exceptions. I believe Twisted was regarded as "UnPythonic" and I've heard complaints about asyncio. Interestingly both are related to the same problem space. I'm sure there are other examples of inscrutable pyramids of abstraction but Python also has many examples of good, readable abstractions. Maybe it's not abstraction that's the problem but poor judgement? What makes one language sprout AbstractSingletonProxyFactoryBeans and another not? I'm not sure if correlates directly to "ability to create abstractions" - I think there's a human element.
- marcosdumay 8y ago> What makes one language sprout AbstractSingletonProxyFactoryBeans and another not? On this case, it's the lack or availability of alternatives. You can't take away every feature that isn't the strictest possible OOP and expect people to create expressive and simple code. Remember that those things were created by the best professionals around, and won the selection of best practices and standard open source tools. People do not write singletons in Python because it has actual static variables. Any moderately complex Python code is full of factories, but people don't even notice because they are actually just a function declaration around the same code that would be there anyway. There is a lot of "writing too the interface" that Java people do with abstract classes, but it comes nearly automatically with duck typing. And for the "bean" part people just use dictionaries. Go people seem to be taking the wrong lesson from those things.
- jacques_chester 8y ago> What makes one language sprout AbstractSingletonProxyFactoryBeans and another not? Missing features that enable higher levels of composition and abstraction. Such as, for example, generics. If you look at the usual punching bag in these discussions, Spring, you will see that as Java added features, the implementations became simpler. Java's design hypothesis was: if we take away these features, median programmers won't blow their feet off accidentally. But it turns out that to get things done you need well-above-median programmers to provide tools, APIs, frameworks, leverage and that in turn, they will need to find a way to do it. In the case of Spring that's been marked by a slow retreat from heroics into progressively simpler approaches as and when Java itself allowed for them. Spring 5, for instance, sets Java 8 as the minimum, and they went through the whole framework refactoring, cleaning up, cutting down and so on wherever that baseline allowed them to. Disclosure: I work for Pivotal, which sponsors Spring, though on unrelated software. But I've spent a lot of time with Spring folks. They are smart.
- sdegutis 8y ago> Lots of abstract classes such as "Processor", "Context", "Box", "Request" and so on, with no real meaning. This is a completely false dichotomy. Just because a feature can be misused doesn't mean it's not legitimately useful. Generics make the language easier for the user, but are harder for the implementors. Most language designers / implementors recognize that this trade-off is worthwhile, and therefore nearly every other popular typed language has them. The Go implementors are the only ones against doing it, because, well, they would have to implement it, and they're concerned that it will make the codebase too complex for them to maintain. (Their sympathizers are against it too, which is still a mystery to me.)
- nemothekid 8y agoI'd argue that Generics make the language easier for the writer, not the reader which is what he was getting at. What I like about Go is the language is small, so at the expense of some verbosity, that in most cases you write once, its very easy to understand. One thing I dislike about some Java & Scala codebases is the "manual" unwinding you have to do to debug some functionality.
- whateveracct 8y agoThis is FUD. Parametric polymorphic code is not hard to read at all - in fact, it is easier to reason about since it is universally quantified for all types.
- chowells 8y agoMost people don't even know what parametric polymorphism is, let alone have ever used a language that supports it. I find it's basically impossible to explain just how powerful of a tool it is to someone who lacks experience using it.
- sdegutis 8y ago> Today it exists in Standard ML, OCaml, F#, Ada, Haskell, Mercury, Visual Prolog, Scala, Julia, and others. > Java, C#, Visual Basic .NET and Delphi have each introduced "generics" for parametric polymorphism. https://en.wikipedia.org/wiki/Parametric_polymorphism https://en.wikipedia.org/wiki/Parametric_polymorphism
- chrnad 8y agoI think it's hard to get a feel for how it would look in practice from those short examples. Library code might be a little ugly and abstract, but as an end-developer, you usually don't need to worry about it. The code you yourself would see and write is going to have a lot more meaning and be more understandable. I've rarely had to do anything fancy with generics in my own projects. Where I've seen it be most useful is when I want to offer a flexible public API -- in which case, the choice of writing a bit of generic library code is much less smelly than copy pasta.
- ori_b 8y ago> Library code might be a little ugly and abstract, but as an end-developer, you usually don't need to worry about it. But the functions you provide for the code you're writing are a library for someone else.
- JepZ 8y agoI am not quite fond of the idea of having C++/Java Style Generics (I simply don't like the syntax that comes with it), but I see that the current system is broken. From my current perspective I think fixing some things related to Go Interfaces and Container types (Maps/Slices) should make them even more usable than they are now and making them pretty much the thing people want to use Generics for. Currently, it looks like hell to build something with the empty interface and using a lib which is built around the empty interface doesn't look nice either. Using Go maps is sometimes awkward too as they require some weird attribute 'comparable' which some types posses and others don't. Why??? I mean why can't the Go authors use their own language construct and use an Interface like: interface Comparable { Comparable(interface{}) bool } Similarly, I find it awkward that we still can't use Interface Slices[1]. I mean I understand it isn't simple to implement but certainly not impossible. One thing on my todo list is writing an experience report with better examples. It's priority just got up-voted ;-) [1]: https://github.com/golang/go/wiki/InterfaceSlice https://github.com/golang/go/wiki/InterfaceSlice
- heavenlyhash 8y agoCounterpoint: coming from a Java background, I find totally freely-definable interfaces for some features used by core container types to be a little worrysome. In Java, I can't count the number of times I've debugged either correctness bugs or 'merely' horrible performance bugs based on incorrect implementations of Equals or Hashcode methods. Implementing a non-commutative Equals method is a colossal footgun, and incredibly irritating to debug, and yet somehow it happens time and again. And there are other, subtler, arguably "correct" and yet but extremely unwise things one can when do given Java-like maps implemented on the Equals and Hashcode interface methods. For example, one can define two types T1 and T2 which both claim to be (commutatively) equal, and define some interface TX which is implemented by T1 and T2. Using a Map<TX,Object> and inserting members by T1 and T2 now has an interesting form of semi-defined behavior: if inserting "equal" keys, whichever type was used first will be persisted. Imagine doing this in some concurrent map, and imagine that T1 is 8 bytes and T2 is 400 bytes; enjoy debugging those occasional OOMs. I've been very happy not to ever have this particular problem when defining the key types in my Go maps.
- orblivion 8y agoI'd say generics aren't just about avoiding copy-pasting. It's also added type safety. At this point the closest thing is using an interface as a function parameter. You get a dynamically typed value (albeit limited to types that implement the interface).
- lima 8y agoThe closest thing is auto-generating code, not using interface{}.
- platinumrad 8y agoYes, but there's still a depressingly large amount of code in the wild that just casts interface{} back and forth.
- lima 8y agoCommon with ORMs. The gorm API is horrible - it's all interfaces. The only reasonable ORM library I've seen is sqlboiler, which uses code generation. https://github.com/volatiletech/sqlboiler https://github.com/volatiletech/sqlboiler
- orblivion 8y agoI don't necessarily mean interface{}. This: type Thing interface { ... } is better, but Thing is still a dynamic type.
- sacado2 8y agoThat, and efficiency, as the generated code can be inlined in many cases.
- grey-area 8y agoSome abstractions have stood the test of time - map, reduce, sort, those are worth having and various other collection types would be nice to see. I agree the introduction of generics is a big worry not because of what good programmers will do with it, but because of what people more enamoured with pretty abstractions than code that does work will do with it. I don’t particularly want to live inside someone else’s creation for weeks just to understand it. The existing culture of no nonsense and minimal abstraction should help police that though. I’m not that keen on the syntax of contracts either, but we’ll see what comes out at the end, that’s not so important - the idea of contracts looks like a good one in theory at least, but it feels like they could have used interfaces instead somehow (I’m probably missing something there). The errors proposal is interesting as it looks like try/catch at first glance but importantly doesn’t leak that error across function/lib boundaries as exceptions do, so it’s a natural extension of the existing approach.
- lazyjones 8y ago> I’m not that keen on the syntax of contracts either, but we’ll see what comes out at the end, that’s not so important - the idea of contracts looks like a good one in theory at least, but it feels like they could have used interfaces instead somehow I'm not sure I understand the need for contracts really. If a function with type argument is compiled, the caller should use specific types to call it, so the compiler can determine whether said type has the required methods used in the generic function. Therefore the compiler knows everything it needs to fail with a compile-time error without any extra ugly "contract" syntax. What am I missing? Are some types only resolved at runtime?
- summerlight 8y agoThat's exactly what C++ is doing for their template. The problem here is that if you want to use some generic functions, then you need to look into its implementation to see what kinds of requirements are needed for its type. And if you failed to meet the requirement, you'll very likely get several hundred lines of cryptic compiler error message; C++ compilers have been trying very hard to make the error message readable but it's still an awful experience. Also, an implementer now needs to be extra careful when they want to update the function since a single line of seemingly innocuous change may bring breakages for downstream users. Of course this can be prevented by carefully written exhaustive tests, but then what's the point of not having contract? So the lessons from a number of PL implementations show that this kind of information needs to be explicitly encoded in codes, and that's the whole point of having "contract", "type class", "concept" or whatever else it's called. Compilers may be able to infer technical details of type, but at least for now it's not sophisticated enough to infer the original intention of the writer.
- mikeschmatz 8y agoPoor design has nothing to do with generics. If anything they make API cleaner and safer to work with.
- oihoaihsfoiahsf 8y ago> "Processor", "Context", "Box", "Request" There are a lot of interfaces in the existing library that one could consider quite abstract as well. Reader? Writer? error? Another thing worth noting is that many/most C++ codebases manage to avoid the type of generic proliferation you're talking about.
- mike_hearn 8y agoI'm not sure C++ is a great example of a language that avoids generics abuse. The reason STL errors are so convoluted is largely due to the unintuitive and complicated way they use generics to support obscure features like changing the allocator used by containers. Java is actually a much better example of where generics are used rarely and well. Containers are parameterised by exactly one thing - the type of what they contain. You don't parameterise them with different allocation strategies or comparators. Type inference means generics are often automatic, but still catch mistakes for you. And whilst nothing stops people making bad abstractions in any language, the standard library sets a pretty good example by and large. That said, Kotlin's generics are better still and probably the best implementation I know of.
- grandmczeb 8y ago> The reason STL errors are so convoluted is largely due to the unintuitive and complicated way they use generics to support obscure features like changing the allocator used by containers The main reasons template errors suck so much in C++ are 1) lack of a proper mechanism to constrain instantiations (hopefully addressed by Concepts) and 2) The general mess that is function overloading and automatic type conversions. For example, the classic "template hell" error message people usually encounter when starting to use C++ is failing to properly overload operator<< (example here[1]). That error message could easily be as simple as "No `operator<<` for class C", but instead the compiler needs to inform the user of every possible overload match, every possible type conversions that could cause a match, and templates that could have matched but can't be instantiated along with a reason for the failure. Of course it doesn't help that std::ostream is absurdly abstract, but it's really just making a bad problem worse rather than the fundamental root of the problem [1] https://godbolt.org/z/XveitP https://godbolt.org/z/XveitP
- jballanc 8y ago> One cannot have both I guess? Not to be snarky, but... The whole point of LISP-style macros is that you very much can have both. Ultimately, generics will compile down, either via type erasure or a generated specialization, to a "copy-pasted" function (in concept, at least). With a macro, you have ultimate control of how this is achieved. The problem, of course, is that the people who care how generics are implemented all think their way is the best (leading to a proliferation of slightly-different-and-incompatible implementations), and everyone else just wants something that works fast and reliably. Bad code can be written in any language. Good code can be written in most languages (yes, even C++...the jury is still out on PHP). Every feature that grants power comes at the cost of potential complexity. What ultimately determines success or failure is the community and best practices that develop around these features. In this regard, I think Go will be "o.k." even with generics.
- platinumrad 8y ago>The second type of productivity - which is getting more and more important - is across projects and codebases. And that gets thrown out of the window with generics. Generics increase type safety (no more interface{} casts) and reduce code duplication. I don't see how this could possibly throw productivity "out the window". If anything, the effect will be the opposite. The tradeoff with generics has always been programmer time versus compile time and/or execution time. Assuming that programmers don't unnecessarily complicate things for themselves, programmer time will be significantly lessened with the introduction of generics. And there's nothing preventing programmers from burying themselves in a sea of abstraction even in a language without generics.
- dangjc 8y agoI NEED generics to do any quantitative code. I evaluated Go for a project, and completely ruled it out because of this. You're unable to write an algorithm for float 32s and 64s, scalars vs vectors vs matrices, etc. There might be a bias in the commentary because the membership of the Go community has self selected to just be those for whom the current constraints are feasible.
- giovannibajo1 8y agoYou won't be able to do numeric stuff with these proposals as well. You would still be missing operator overloading and parameter of integer types, which are the basis of any generic numeric code.
- garfij 8y agoTrue about no operator overloading, but that's just syntactical sugar for functions. I believe you can get integer parameter types by using something like the `convertible` contract seen here https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md#passing-explicit-types-to-a-contract https://go.googlesource.com/proposal/+/master/design/go2draf... (unless I'm mistaking what you are saying)
- sanxiyn 8y agoI believe you are mistaking. C++ allows things like std::array<int, 3>, where 3 is parameter of integer types.
- majewsky 8y agoIf you need a generic algorithm where the possible types are very finite (e.g. just float32 and float64, or just NxM matrices for N,M=1,...,4) you might be able to `go generate` the concrete implementations from a text template.
- jacques_chester 8y agoText generation is fragile, error prone, does not play well with other type-based tooling, is difficult to upgrade, adds unnecessary busywork to the programmer's day. When Java's limited, half-crippled version of generics showed up, sourcecode generation died. Pretty much overnight. Why doesn't anyone do it? Because it's a terrible experience compared to having types.
- random3 8y ago> I believe Golang has two different kinds of productivity. Productivity within the project goes up quite a lot with generics. And then, everyone tends to immediately treat every problem as "let's create a mini language to solve this problem". The second type of productivity - which is getting more and more important - is across projects and codebases. And that gets thrown out of the window with generics. This is a very valuable observation, relevant outside of Golang, which underlines the pitfalls of abstractions. Design patterns aim to standardize abstractions so that other readers of the same code can easily understand. I used to capture the same related principles in what I called "IKEA code" - simple, elegant code that is cheap to write, easy to understand and use and not meant to last forever. I.e. code and design that is meant to be replaced. I think the concept of "local productivity vs global productivity" is very related and completes the "IKEA code" principles. Rather than suggest one is better than the other, I think it's important to understand the motivations when making a choice.
- jy3 8y agoMy gut feeling is a significant part of people who voted for them don't really intend to use Go that much. They might try to please different crowds with Go 2. At the end of the day, they might just loose everyone.
- deleted 8y ago[deleted]
- KirinDave 8y ago> But then, after seeing the examples, e.g. https://github.com/albrow/fo/tree/master/examples/ https://github.com/albrow/fo/tree/master/examples/, I could see the picture of what the current codebase would become after its introduced. Lots of abstract classes such as "Processor", "Context", "Box", "Request" and so on, with no real meaning > Compared to a half-as-useful Java codebase where I have to carry a dictionary first to learn 200 new english words for complicated abstractions and spend a month to get "assimilated", this was absolute bliss You're making a good argument for standard generics and their machinery included as part of the standard library as opposed to bolted on. Java incrementally baked generics in relatively late in its life along with a culture of a lot of ad hoc machinery to cover up the lack of useful lambdas. The wisdom of Go putting these in the stdlib is you get their benefit universally without having to wade through a learning process with a uniquely flawed and ad hoc hierarchical ontology for each project. Having such universal tooling is a sign that your language is powerful enough to lift out and abstract real problems. That's a good thing! Don't mistake Java's legacy issues for issues Golang might have. It probably won't end up with the same problems because it starts with a better feature set.
- manigandham 8y agoHaving generics isn't related to excessive abstractions. One is a language feature and the other is program architecture. It's definitely not an exclusive trade-off. I'm sure there are plenty of Go apps with bad architecture, where a tiny dose of generics would greatly help.
- stcredzero 8y agoHaving generics isn't related to excessive abstractions. I've seen the situation in C++ where templates led to excessive templates, simply because they were "neat." As a result, I found myself debugging code that had no source code whatsoever.
- norswap 8y agoComplete bullshit. Generics have 0 relationship or co-dependency of any kind with the abstraction slash design patterns you mention. These are entirely possible without generics, and generics does not in any way encourage them.
- nostrebored 8y ago... I'm sorry, that's a bold claim when a large chunk of these patterns are literally used to allow for you to be able to operate over a variety of different objects in a loosely coupled fashion
- stcredzero 8y agoThere are less common design patterns particular to generics. The presence of generics does tend to encourage those. I have seen subsystems where coders used C++ templates where they could have used polymorphism instead.
- al2o3cr 8y ago> Lots of abstract classes such as "Processor", "Context", "Box", "Request" and so on, with no real meaning. A programmer can write Java in any language if they're willing to work hard enough at it.
- pjmlp 8y agoActually Java adopted those ideas from 90's C++ code style.