35 ms·
Go Contracts – Draft Design
- kyrra 7y agoIf you poke around the files ending with .go2 here[0], you can see some examples of contracts/generics being used. This linked review is a prototype of the implementation for the proposal. [0] https://go-review.googlesource.com/c/go/+/187317 https://go-review.googlesource.com/c/go/+/187317
- NiceGuy_Ty 7y agoThis is why even the most basic form of generics would be amazing for reducing boilerplate: https://go-review.googlesource.com/c/go/+/187317/3/src/go/parser/testdata/set.go2 https://go-review.googlesource.com/c/go/+/187317/3/src/go/pa...
- drej 7y agoI read through this yesterday and I'm fairly confused about all that. 1. This is a long proposal. Excluding the implementation/issues/comparison parts, it clocks in at 7700 words. Compared to the 27k-word long Go spec, this is epic. I know the proposal is more verbose than the eventual spec (if accepted), but still... 2. There are some valid points in Nate Finch's criticism of the previous proposal (https://npf.io/2018/09/go2-contracts-go-too-far/ https://npf.io/2018/09/go2-contracts-go-too-far/). Be it philosophical arguments or just plain syntax ones - like writing `func (...)(...)(...)` in function declarations. I don't know. I'd like some form of generics in the language, but I'm not sure if complex designs like this are likely to be embraced by the community.
- Cthulhu_ 7y agoI think the complexity / length is a good indication as to why there's resistance to adding generics to Go; frequently, Java generics are cited since (and this is from the top of my head) it almost doubled the size of both the Java spec and its implementations (compiler, runtime). That is a lot of complexity to manage, and runs counter to what I believe is the current mindset of the Go language. And in Java it didn't stop there; the people (person?) behind generics moved on to create Scala, a language with even more features and a notoriously complicated 25-step compilation process (see https://typelevel.org/scala/docs/phases.html https://typelevel.org/scala/docs/phases.html) (compare with Java's six phases https://www.javatpoint.com/compiler-phases https://www.javatpoint.com/compiler-phases and Go's... three? Not sure, I found https://getstream.io/blog/how-a-go-program-compiles-down-to-machine-code/ https://getstream.io/blog/how-a-go-program-compiles-down-to-...). edit: Actually most of my knowledge comes from https://news.ycombinator.com/item?id=9622417 https://news.ycombinator.com/item?id=9622417, I had bookmarked it.
- apta 7y ago> And in Java it didn't stop there; the people (person?) behind generics moved on to create Scala, Java and Scala are two very different languages, it doesn't makes sense to compare them. What you're suggesting is basically a slippery slope fallacy.
- msbarnett 7y ago> And in Java it didn't stop there; the people (person?) behind generics moved on to create Scala, a language with even more features and a notoriously complicated 25-step compilation process What does this have to do with anything? Scala is complex to compile for reasons far, far beyond generics. Are generics somehow guilty by association because an engineer who was once involved in an implementation of them went on to build some other complex thing?
- cinnamonheart 7y agoI don't think generics necessarily need to have a complex specification. Standard ML's specification, despite parametric polymorphism and parameterised modules, is incredibly small and easy to understand.
- Footpost 7y agoI'm afraid that's not the case. Generics are very easy to implement under the following reasonable conditions: 1. you don't care about performance (you simply 'box' everything), 2. you don't care about executable size (you simply specialise every generic definition to their concrete use cases -- this is what C++ compilers do), 3. you don't do reflection on types in generic definitions. As "cinnamonheart" mentions in a sibling post, ML's generics are straightforward, and remain, despite hailing from the 1970, even today a shining example of programming language design. Unfortunately, Scala had to violate all three points above to maintain compatibility with Java, and the JVM.
- erik_seaberg 7y agoThe compiler should do as much work as it possibly can. The alternative is making me do it at 0.0001 MHz because I'm made of meat.
- benhoyt 7y agoThe draft design is long, however, I don't think by itself that's an indication of complexity, and this would be a fraction of the size in the actual spec. This is tutorial-like in places, with many code examples (more than a third of the doc), and a whole section (somewhat less than a third) on issues and discarded ideas.
- the_duke 7y agoI agree that the proposal is far from succinct. But it is not really written a s a technical proposal, more an explanation with plenty of detail and abundant examples. The important information is kind of drowned out by the noise. But other than that it is not really that complex. Generics aren't easy, but they are not something that needs to be invented. Implementation options and tradeoffs are well understood. The duplication introduced by having both concepts and interfaces is unfortunate though. Regarding syntax: No language has really managed to provide a really good syntax for generics, imo. The default <T> is often ugly. Haskell chose to have them declared in a separate function header which is much nicer, but also annoying when having to modify two lines, etc. I think this is a reasonable tradeoff. With special syntax highlighting for `(type T)` it will be easy enough to parse visually. The proposal does contain some particularly unfortunate examples though.
- skybrian 7y agoIt doesn't have contracts, but Zig's syntax seems pretty good: https://ziglang.org/documentation/master/#comptime https://ziglang.org/documentation/master/#comptime
- 013a 7y agoThis proposal is so hilariously complex relative to everything else in Go, I have to wonder if the Go team has already decided that they aren't adding Generics; they release a galaxy brain generics proposal which even the strongest proponents of the feature have to admit is way too much, it inevitably gets struck down, and now they can say "look team, we tried and you said you didn't want generics." Genius level play from Ian and Robert.
- geodel 7y agoPerhaps a 3-part medium post or a series of 10 tweets be more suitable for most discussed (lack of) feature for years?
- deleted 7y ago[deleted]
- JyB 7y ago> but I'm not sure if complex designs like this are likely to be embraced by the community. Spoiler alert: It won't. If you think the Go community had a strong reaction with `if err != nil`, just wait for this one.
- iainmerrick 7y agoWhy “Contracts” and not “Generics”? To make it less contentious, maybe, or to manage expectations?
- jsty 7y agoProbably because they don't want people talking cross-purposes or confusing their proposals with what is implemented in other languages, as they call out in the intro: "As the term generic is widely used in the Go community, we will use it below as a shorthand to mean a function or type that takes type parameters. Don't confuse the term generic as used in this design with the same term in other languages like C++, C#, Java, or Rust; they have similarities but are not the same."
- iainmerrick 7y agoThose four languages all have completely different implementations of parameterized types, but calling them all “generics” doesn’t add any extra confusion. To the contrary, it’s actually helpful to use the same word for something with roughly the same uses (eg generic type-safe container classes) even if the details are very different.
- jsty 7y agoI don't particularly disagree. My point was more that given what the authors wrote in the quoted sentence, I thought that - rightly or wrongly - was likely the reasoning behind the naming decision.
- nemith 7y agoContract is just the feature is defines the abilities of the genericized types. A contract in Go enables generic programming or as other state it "generics". In C++20 there is concepts which is very similar. https://en.wikipedia.org/wiki/Concepts_(C%2B%2B) https://en.wikipedia.org/wiki/Concepts_(C%2B%2B) in constraining the generic type before template expansion.
- 7y ago
- cjslep 7y agoI haven't been following these proposals and I want generics of some kind in Go (I've had my fill of code generation), but good God does the proposed syntax of using parentheses for yet another thing (in addition to method receiver, parameters, return types, logical grouping, arguments) make the language suddenly feel cluttered and hard to read.
- eternalban 7y agoAnd it is basically notation noise. The idea is to inform the compiler as to which type names are generic. // proposed func Print2(type T1, T2)(s1 []T1, s2 []T2) { ... } vs // less noise func Print2(s1 []'T1, s2 []'T2) { ... } This proposal does -not- seem to be informed by the same 'outer space' mindset that gave us the Go language. A reminder that mere capitalization in Go language informs visibility and access. Beyond mere syntax issues, the semantics of generics itself is adding huge complexity to what is a very capable and accessible 80% language.
- Insanity 7y agoI really don't think generics (or contracts) will benefit the language. Most people who miss it are just missing the collection frameworks from Java/C#, and there are solutions to this anyway. (go:generate being one of them if you really need collections for X types). I did miss generics when I first started writing Go, but after doing so for about a year at work and 2 years before that, I don't find me missing them at all.
- JyB 7y agoI have the exact same feeling. I was using generics extensively in C# particularly. I don't miss them at all after having spent a lot of time working with and understanding Go. I can't help but feel it will just make the language far less attractive in the long run.
- Insanity 7y agoYeah I believe so as well. All languages having the same features would also be a downside. Just make a choice and stand by it. (Like Haskell, they are not pressured into doing things just because Java does so).
- justanegg 7y agowhat does this allow you to write that go1 doesn't?
- 013a 7y agoI don't like this at all. This is a classic example of a feature where, in the right hands its very powerful, but in the wrong hands it will ruin the simplicity of the logic it touches. Go's magic was really in its ability to be placed in the wrong hands and still end up with a serviceable program.
- dgellow 7y agoI've seen wrong hands doing terrible things with combinations of `interface{}` and reflections!
- stcredzero 7y agoThis has been a story in programming for literally decades.
- ailideex 7y agoI find the position that the real solution to incompetence is to just give people blunt knives to be utterly unsatisfactory. If someone cannot understand how exceptions work for example I have a hard time seeing how removing this massive burden from their minds will result in good outcomes. Being easy to use for people that probably would be better off doing something else is not a solid design principle. Java went down this route, ah, we will remove operator overloading, that will make it simpler, because nobody can ever write a function named equals which does not do what you expect it to... and the end result is horrible and does not prevent people from doing amazingly dumb things.
- tfha 7y agoAs a reliability engineer I disagree. It's nice to know that your code has few antipatterns in it by design. "Blunt knives" is a really bad way to think about it. You have plenty of very sharp knife, and they are easy to use and have clearly defined scope and purpose. A knife that's sharp and unweildly makes me nervous even in the hands of an expert. Keep unweildly constructions out of go!
- deleted 7y ago[deleted]
- JulianMorrison 7y agoIt seems as a proposal to overlap interfaces a lot. - Interfaces gather methods. The concrete type referenced through an interface doesn't need to know about the interface. Interfaces can be used to decouple operations from the details of the thing being operated upon. - Contracts gather methods. The concrete type referenced through a contract doesn't need to know about the contract. Contracts can be used to decouple operations from the details of the thing being operated upon. Disjunction: Interfaces are resolved at runtime and result in indirect calls. Contracts are resolved at compile time and their contract-nature is lost in object code, they just become direct calls. Oh and contracts can do operators. It feels to me like the risk here is not more syntax but less orthogonality. There will be two features competing to be the standard way to do the contract-like things.
- logicchains 7y agoContracts support multiple "receivers". If I want to write a function that converts between two arbitrary types, I can write a `func convert(Type1 Type2 Convertible)(a Type1, b Type1){...}`, where Convertible in a contract with one method that takes a Type1 and receives a Type2. There's no way to do the same thing with interfaces as an interface can only reference a single type, a single receiver.
- jerf 7y agoAnd on the flip side, interfaces allow heterogeneous lists; []io.Reader can have several different distinct types that implement io.Reader in them, whereas the contract version would be "a slice of a type that implements io.Reader" but it would have to all be the same type. I won't deny they're not orthogonal, nor that we're probably going to see some confusion about which to use when from beginners, nor that there are probably interfaces in the wild that really ought to be contracts (I'm pretty sure I've got a couple myself, though I haven't fully checked until the proposal is stabilized), but neither do they quite stand in for each other. Perhaps if this was in the language from day one, more work would be put in to finding a way to make just one "interface/contract" feature with some kind of (probably confusing) parameter in it that lets it serve both purposes, but doing that today doesn't seem to be on the table.
- koblas 7y agoThe proposal has a very short statement about implementation. If you think about this very carfully, you realize that part of the compilation speed of go allows you to compile a single package into code without having to leak out all of you abstractions. If you have contracts/generics then you need to have un-compiled code as part of your exports. Which is a huge break from the current approach.
- logicchains 7y ago>We believe that this design permits different implementation choices. Code may be compiled separately for each set of type arguments, or it may be compiled as though each type argument is handled similarly to an interface type with method calls, or there may be some combination of the two. If the latter approach is taken then no un-compiled code needs to be exported.
- munificent 7y agoYeah, I'm very interested in how they plan to integrate this into separate compilation. My understanding from C++ and C# is that this is a hard problem.
- steveklabnik 7y agoIn Rust, we end up storing the necessary metadata inside of the pre-compiled library, so that when you use it from another library, the compiler knows how to do the right thing.
- pjmlp 7y agoD, Delphi, Eiffel compile just as fast, just to cite three possible examples with AOT compilation to native code as their default toolchain.
- wybiral 7y agoThis is too similar to interfaces for me and seems like it would only add to the complexity of the language, documentation, and compile time for a small increase in generic code. One of Go's core strengths is simplicity. I've personally witnessed both C and Python programmers jump right into Go projects with minimal assistance. Learning new packages is absurdly easy because the code is so simple and documentation practically generates itself. I worry that widespread use of this feature would negatively impact the language in that regard. Lacking a true "generics" feature has had an influence that people tend to overlook, which is that the Go community is less reliant on dependencies. One of Go's proverbs is "A little copying is better than a little dependency" [0]. The ecosystem is better documented and healthier because of it, avoiding the require("left-pad") world of the dependency abyss. The language lends itself to being used for smaller, more concise code bases where things are written with specific and narrowly-defined purpose. It implicitly discourages "Architecture Astronauts" [1] and that strength shouldn't be overlooked or sacrificed for a few new container options. [0] https://go-proverbs.github.io/ https://go-proverbs.github.io/ [1] https://www.joelonsoftware.com/2001/04/21/dont-let-architecture-astronauts-scare-you/ https://www.joelonsoftware.com/2001/04/21/dont-let-architect...
- woah 7y agoIs it lack of generics that has resulted in fewer dependencies, or the incredibly full featured stdlib?
- abhchand 7y agoa bit of both?
- quacker 7y agoMissing generics certainly had some influence on golang, but not sure I understand your point. How does adding generics lead to a reliance on dependencies, or the absence of generics reduce reliance on dependencies? The relative lack of "dependency hell" in golang compared to other languages is because of the great standard library. Golang also never had an official/proper (imo) solution for package management until just this past year or so (although people worked around that a bit)
- yorozu 7y ago> Type assertions and switches I find it especially unorthogonal that you can type switch on generic parameters like they are interfaces. If I have a method that takes interface parameters, should I just always use generic (unboxed) parameters instead?
- lalaithion 7y agoI still think that, for the most part, Go doesn't need contracts (yet). I think it's common, coming from the object oriented world, to expect polymorphism to be intrinsically tied to method overloading. But it doesn't have to be. Of the 12 examples at the end of the draft, there is only one real contract used, `comparable`, which could be easily done without contracts (but with generics) by passing in an extra argument `lessThanOrEqual func(T, T) bool`. The other kind of contract is harder to do without contracts, as it is the contract which combines all numeric types. However, most of the examples would be possible to write, albeit a little more messily, with a parameter `toDouble f(T) double`. Alternatively, we could go the route of baking in a few special contracts into the language, such as a numeric contract, just like how map and slice are baked in polymorphic structures. What do we gain from writing code this way? It leaves the Go language with more freedom from the future. Everyone agrees Go needs polymorphism. So add polymorphism. But as we can't agree on contracts, we can just leave it out. We'll get 90% of the benefits upfront, and then we can wait and see what are the actual edge cases we run into with contracts, and decide where to go from there.
- stcredzero 7y agoI think it's common, coming from the object oriented world, to expect polymorphism to be intrinsically tied to method overloading. But it doesn't have to be. Please provide some examples. For example, I don't think polymorphism in direct variable references is all that good. It leaves the Go language with more freedom from the future. Everyone agrees Go needs polymorphism. So add polymorphism. This statement confuses me. Go has polymorphism.
- uryga 7y ago> Go has polymorphism polymorphism is often distinguished into ad-hoc polymorphism (interfaces) and parametric polymorphism (generics) – i'm guessing GP was referring to the latter?
- lalaithion 7y agoYou're right, in my head I was writing parametric polymorphism, and it just didn't make it's way to my fingers somehow.
- yamann 7y agoGolang is a joke that went too far. It doesn't take 100 IQ points to understand that Russ Cox and his minions don't understand a thing about modern (or even old) language design. This is a language designed for employers not for developers.
- arendtio 7y agoSome people here seem to not like the similarity to interfaces, but I think, it should be even more like interfaces. In fact, I like the interface syntax even better than the contract syntax. And the type parameter list pollutes the otherwise very clean syntax in my opinion. So you might ask what I am suggesting. AFAIK there are two major problems preventing interfaces to be used as generics: 1. There is no way to require the methods of builtin types aka operators. 2. Compile-time type safety is very limited The reason why interfaces have no access to operators and other language features is that the language is kinda rough around the edges. And I really don't want to hurt anybody with this statement. I love the language, but I think there are some things that aren't perfect. For example, just imagine every array/slice would have a method (e.g. Position(int)) that would be the equivalent of the square brackets and the square brackets would be just some syntactic sugar like arrayA[0] == arrayA.Position(0) Same thing for operators like + x := 1 x + 2 == x.Addition(2) It would eliminate a lot of cases that make contracts necessary. In essence, it would mean that you could actually address built-in types in interfaces. The second problem is related to the type parameter list. In my opinion, it would be better to simply put those types on extra lines like: type T Generic func Print(s []T) { // function body } I mean, there should be no problem in making them available for more than one function and it would definitely clean up the function signature and look more Go-like.
- baby 7y agoThat looks much better! Somewhat like associated types of rust.
- n13 7y agoAgree & Voted. Another alternative that I would like is to have angle brackets < > for better readability, so that definition and call would look as follows: // 1 func Print2<type T1, T2>(s1 []T1, s2 []T2) { ... } Print2<int,int>( ... ) // 2 var v Vector<int> // 3 func (v *Vector<Element>) Push(x Element) { *v = append(*v, x) } Sorry, if it gives you C++ nightmares.
- atombender 7y agoAgreed, though I think the syntax problem (too many parens) could be made more readable and Go-like by emphasizing the generic aspect and making it stand out visually -- leaving no doubt that you're in a section that's generic, and perhaps dissuading overuse by making it noisy. Perhaps something like: generic func(T, U) Map(slice []T, mapper func(T) U) []U For "contracts": type Set generic interface(T comparable) { Push(T) Pop() T } Every time you want to refer to a generic type you'd have to use the generic keyword, which encourages use of concrete types: type IntSet generic Set(int) This is more or less how Modula-3 does it. Generics have dedicated syntax with a "generic" keyword.
- baby 7y agoI really see no reason for these. The difference could be easily seen in the types themselves. I’ve posted that elsewhere but you could have an func thing(stuff _.T, stuff2 _.k) Or func thing(stuff g.K) And so on. Also want to point out that there is a LOT of Golang code that is being shipped and used in the world and none of it uses generics. Do we really need them?
- continuational 7y agoA lot of assembly code was being shipped before we had better languages. That's no reason to be stuck with a type system of the previous millennium.
- baby 7y agoI honestly think generics are like oop. Vastly overestimated.
- iamgopal 7y agoWhy not extend the interface ? It would be less of an interface and more of a generic but may provide easier upgrade path, wouldn't it ?
- mseepgood 7y agoThere's a whole section in the document literally titled "Why not use interfaces instead of contracts?"
- tmaly 7y agoI think the examples at the end of the document really show the power of the proposal. But, there are some aspects that will make code appear more complex or challenging to read. It will certainly make it more challenging for beginners. Appearance of the code with type switches and issue with the idea of Iterator and doing something two different ways were the only parts that stood out to me besides increased complexity
- AlexeySoshin 7y agoGenerics is a tool for library developers mostly. Do beginners often read library code?
- hota_mazi 7y agoTook me a while to realize that this is not about "contracts" the way this concept is generally understood, but Go's versions of generics.
- samprotas 7y ago"Although functions may have multiple type parameters, they may only have a single contract." Anyone else find this limitation a bit disappointing? Seems like a somewhat arbitrary restriction that limits the usefulness of this feature. I hope it doesn't take another 10 years for this to be changed...
- monkeyfacebag 7y agoit looks like contracts are composable the same way interfaces are so I don't know how much of a limitation this will be in practice.
- samprotas 7y agoSo I am a bit unclear on this from the proposal. Composing contracts is a slightly more verbose fix for allowing multiple contracts for a given type (just make a composed contract and specify that). Using composed contracts, allowed: func Foo(type T PrintStringer)(s T) {...} I read this as, while a function can have multiple type parameters, only one contract can be specified in total. Not allowed (function uses "setter" and "stringer"): func Bar(type B setter, S stringer)(box B, item S) {...} Maybe I'm misunderstanding though. In other languages with parametric polymorphism, the real re-use comes in by allowing functions like Bar to be used for any combination of "constraint implementing" types.
- monkeyfacebag 7y agoI think you're correct and I misread that part of the spec. That does seem to be an important limitation.
- msbarnett 7y ago> Maybe I'm misunderstanding though. As I read it, while you can't do: func Bar(type B setter, S stringer)(box B, item S) {...} directly, you accomplish the same thing via contract SetterStringer(B, S) { setter(B) stringer(S) } func Bar(type B, S SetterStringer)(box B, item S) {...} so in practice it's basically the same thing, you just have to explicitly specify the contract the function conforms to via a composition of the two other contracts.
- mongol 7y agoIt is interesting to compare the mostly negative reception here with the more postivive over at reddit.com/r/golang [1] Granted, that community in average probably contains more "Go fans". There was no lack of criticism at the try proposal over there though. 1: https://www.reddit.com/r/golang/comments/cifdwf/the_updated_and_simplified_draft_design_for_go_2?sort=confidence https://www.reddit.com/r/golang/comments/cifdwf/the_updated_...
- dnr 7y agoThis is really bike-shed-y, but: I'm disappointed they didn't go with «» or similar to set apart type parameters. The answer in the doc is that they "couldn't bring ourselves to require non-ascii", but there's an easy way to handle this without _requiring_ non-ascii characters: let "(type …)" and "«…»" be syntactically identical, and have gofmt change the former to the latter. Basically everyone uses gofmt all the time, so all the code everyone reads would use the more visually distinctive syntax, but it would be easy to type using ascii only.
- zemo 7y agotbh I just want sum types but whatever
- iio7 7y agoI cannot fully express the frustration I deeply feel with people constantly proposing changes to Go that will turn it into something that is no longer Go! I know where this specific proposal is coming from, but I feel that Robert and Ian are being pushed by the constant noise made by people coming from other languages, people who like those who made the horrible "try" proposal, seem to be trying really hard to ruin Go by turning it into yet another complex monster. Not a day goes by without someone making a new proposal that is trying to change the very thing that made Go so unique and lovable! All the proposals that has been made so far exists in several of the other popular programming languages. Use one of those if you really want the added complexity - leave Go just the way it is!
- AlexeySoshin 7y agoThose horrible people coming from "other languages". People should be born straight into Go, or not born at all!
- paedubucher 7y agoHow (and when) does a Draft Design become a Proposal to be discussed officially on GitHub?
- utahcon 7y agoI am totally against generics, if you want generics, go to Python, or somewhere else that allows them. The beauty in Go is that it is static, and generics are not strict. I agree with wybiral [1]. This only serves to add complexity that gains very little, and opens Go to being less strict and there for less reliable and more problematic at compile time . [1] (https://news.ycombinator.com/item?id=20555477 https://news.ycombinator.com/item?id=20555477) edit: changed "strict" to "static"