New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: spec: generic programming facilities #15292

Open
adg opened this Issue Apr 14, 2016 · 408 comments

Comments

Projects
None yet
@adg
Contributor

adg commented Apr 14, 2016

This issue proposes that Go should support some form of generic programming.
It has the Go2 label, since for Go1.x the language is more or less done.

Accompanying this issue is a general generics proposal by @ianlancetaylor that includes four specific flawed proposals of generic programming mechanisms for Go.

The intent is not to add generics to Go at this time, but rather to show people what a complete proposal would look like. We hope this will be of help to anyone proposing similar language changes in the future.

@adg adg added Go2 Proposal labels Apr 14, 2016

@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

gopherbot commented Apr 14, 2016

CL https://golang.org/cl/22057 mentions this issue.

gopherbot pushed a commit to golang/proposal that referenced this issue Apr 14, 2016

design: add "Go should have generics" proposal
This change proposes the notion of generics in Go, and includes four
proposals from years' past that are each flawed in their own ways.
These historical documents are included to show what a complete
generics proposal looks like.

Updates golang/go#15292

Change-Id: Ice1c35af618a043d55ceec54f182d45a4de780e1
Reviewed-on: https://go-review.googlesource.com/22057
Reviewed-by: Ian Lance Taylor <iant@golang.org>

@ianlancetaylor ianlancetaylor added this to the Proposal milestone Apr 14, 2016

@bradfitz

This comment has been minimized.

Show comment
Hide comment
@bradfitz

bradfitz Apr 14, 2016

Member

Let me preemptively remind everybody of our https://golang.org/wiki/NoMeToo policy. The emoji party is above.

Member

bradfitz commented Apr 14, 2016

Let me preemptively remind everybody of our https://golang.org/wiki/NoMeToo policy. The emoji party is above.

@egonelbre

This comment has been minimized.

Show comment
Hide comment
@egonelbre

egonelbre Apr 14, 2016

Contributor

There is Summary of Go Generics Discussions, which tries to provide an overview of discussions from different places. It also provides some examples how to solve problems, where you would want to use generics.

Contributor

egonelbre commented Apr 14, 2016

There is Summary of Go Generics Discussions, which tries to provide an overview of discussions from different places. It also provides some examples how to solve problems, where you would want to use generics.

@tamird

This comment has been minimized.

Show comment
Hide comment
@tamird

tamird Apr 14, 2016

Contributor

There are two "requirements" in the linked proposal that may complicate the implementation and reduce type safety:

  • Define generic types based on types that are not known until they are instantiated.
  • Do not require an explicit relationship between the definition of a generic type or function and its use. That is, programs should not have to explicitly say type T implements generic G.

These requirements seem to exclude e.g. a system similar to Rust's trait system, where generic types are constrained by trait bounds. Why are these needed?

Contributor

tamird commented Apr 14, 2016

There are two "requirements" in the linked proposal that may complicate the implementation and reduce type safety:

  • Define generic types based on types that are not known until they are instantiated.
  • Do not require an explicit relationship between the definition of a generic type or function and its use. That is, programs should not have to explicitly say type T implements generic G.

These requirements seem to exclude e.g. a system similar to Rust's trait system, where generic types are constrained by trait bounds. Why are these needed?

@sbunce

This comment has been minimized.

Show comment
Hide comment
@sbunce

sbunce Apr 14, 2016

It becomes tempting to build generics into the standard library at a very low level, as in C++ std::basic_string<char, std::char_traits, std::allocator >. This has its benefits—otherwise nobody would do it—but it has wide-ranging and sometimes surprising effects, as in incomprehensible C++ error messages.

The problem in C++ arises from type checking generated code. There needs to be an additional type check before code generation. The C++ concepts proposal enables this by allowing the author of generic code to specify the requirements of a generic type. That way, compilation can fail type checking before code generation and simple error messages can be printed. The problem with C++ generics (without concepts) is that the generic code is the specification of the generic type. That's what creates the incomprehensible error messages.

Generic code should not be the specification of a generic type.

sbunce commented Apr 14, 2016

It becomes tempting to build generics into the standard library at a very low level, as in C++ std::basic_string<char, std::char_traits, std::allocator >. This has its benefits—otherwise nobody would do it—but it has wide-ranging and sometimes surprising effects, as in incomprehensible C++ error messages.

The problem in C++ arises from type checking generated code. There needs to be an additional type check before code generation. The C++ concepts proposal enables this by allowing the author of generic code to specify the requirements of a generic type. That way, compilation can fail type checking before code generation and simple error messages can be printed. The problem with C++ generics (without concepts) is that the generic code is the specification of the generic type. That's what creates the incomprehensible error messages.

Generic code should not be the specification of a generic type.

@ianlancetaylor

This comment has been minimized.

Show comment
Hide comment
@ianlancetaylor

ianlancetaylor Apr 14, 2016

Contributor

@tamird It is an essential feature of Go's interface types that you can define a non-interface type T and later define an interface type I such that T implements I. See https://golang.org/doc/faq#implements_interface . It would be inconsistent if Go implemented a form of generics for which a generic type G could only be used with a type T that explicitly said "I can be used to implement G."

I'm not familiar with Rust, but I don't know of any language that requires T to explicitly state that it can be used to implement G. The two requirements you mention do not mean that G can not impose requirements on T, just as I imposes requirements on T. The requirements just mean that G and T can be written independently. That is a highly desirable feature for generics, and I can not imagine abandoning it.

Contributor

ianlancetaylor commented Apr 14, 2016

@tamird It is an essential feature of Go's interface types that you can define a non-interface type T and later define an interface type I such that T implements I. See https://golang.org/doc/faq#implements_interface . It would be inconsistent if Go implemented a form of generics for which a generic type G could only be used with a type T that explicitly said "I can be used to implement G."

I'm not familiar with Rust, but I don't know of any language that requires T to explicitly state that it can be used to implement G. The two requirements you mention do not mean that G can not impose requirements on T, just as I imposes requirements on T. The requirements just mean that G and T can be written independently. That is a highly desirable feature for generics, and I can not imagine abandoning it.

@alex

This comment has been minimized.

Show comment
Hide comment
@alex

alex Apr 14, 2016

@ianlancetaylor https://doc.rust-lang.org/book/traits.html explains Rust's traits. While I think they're a good model in general, they would be a bad fit for Go as it exists today.

alex commented Apr 14, 2016

@ianlancetaylor https://doc.rust-lang.org/book/traits.html explains Rust's traits. While I think they're a good model in general, they would be a bad fit for Go as it exists today.

@ianlancetaylor

This comment has been minimized.

Show comment
Hide comment
@ianlancetaylor

ianlancetaylor Apr 14, 2016

Contributor

@sbunce I also thought that concepts were the answer, and you can see the idea scattered through the various proposals before the last one. But it is discouraging that concepts were originally planned for what became C++11, and it is now 2016, and they are still controversial and not particularly close to being included in the C++ language.

Contributor

ianlancetaylor commented Apr 14, 2016

@sbunce I also thought that concepts were the answer, and you can see the idea scattered through the various proposals before the last one. But it is discouraging that concepts were originally planned for what became C++11, and it is now 2016, and they are still controversial and not particularly close to being included in the C++ language.

@joho

This comment has been minimized.

Show comment
Hide comment
@joho

joho Apr 14, 2016

Would there be value on the academic literature for any guidance on evaluating approaches?

The only paper I've read on the topic is Do developers benefit from generic types? (paywall sorry, you might google your way to a pdf download) which had the following to say

Consequently, a conservative interpretation of the experiment
is that generic types can be considered as a tradeoff
between the positive documentation characteristics and the
negative extensibility characteristics. The exciting part of
the study is that it showed a situation where the use of a
(stronger) static type system had a negative impact on the
development time while at the same time the expected bene-
fit – the reduction of type error fixing time – did not appear.
We think that such tasks could help in future experiments in
identifying the impact of type systems.

I also see #15295 also references Lightweight, flexible object-oriented generics.

If we were going to lean on academia to guide the decision I think it would be better to do an up front literature review, and probably decide early if we would weigh empirical studies differently from ones relying on proofs.

joho commented Apr 14, 2016

Would there be value on the academic literature for any guidance on evaluating approaches?

The only paper I've read on the topic is Do developers benefit from generic types? (paywall sorry, you might google your way to a pdf download) which had the following to say

Consequently, a conservative interpretation of the experiment
is that generic types can be considered as a tradeoff
between the positive documentation characteristics and the
negative extensibility characteristics. The exciting part of
the study is that it showed a situation where the use of a
(stronger) static type system had a negative impact on the
development time while at the same time the expected bene-
fit – the reduction of type error fixing time – did not appear.
We think that such tasks could help in future experiments in
identifying the impact of type systems.

I also see #15295 also references Lightweight, flexible object-oriented generics.

If we were going to lean on academia to guide the decision I think it would be better to do an up front literature review, and probably decide early if we would weigh empirical studies differently from ones relying on proofs.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Apr 14, 2016

Please see: http://dl.acm.org/citation.cfm?id=2738008 by Barbara Liskov:

The support for generic programming in modern object-oriented programming languages is awkward and lacks desirable expressive power. We introduce an expressive genericity mechanism that adds expressive power and strengthens static checking, while remaining lightweight and simple in common use cases. Like type classes and concepts, the mechanism allows existing types to model type constraints retroactively. For expressive power, we expose models as named constructs that can be defined and selected explicitly to witness constraints; in common uses of genericity, however, types implicitly witness constraints without additional programmer effort.

I think what they did there is pretty cool - I'm sorry if this is the incorrect place to stop but I couldn't find a place to comment in /proposals and I didn't find an appropriate issue here.

benjamingr commented Apr 14, 2016

Please see: http://dl.acm.org/citation.cfm?id=2738008 by Barbara Liskov:

The support for generic programming in modern object-oriented programming languages is awkward and lacks desirable expressive power. We introduce an expressive genericity mechanism that adds expressive power and strengthens static checking, while remaining lightweight and simple in common use cases. Like type classes and concepts, the mechanism allows existing types to model type constraints retroactively. For expressive power, we expose models as named constructs that can be defined and selected explicitly to witness constraints; in common uses of genericity, however, types implicitly witness constraints without additional programmer effort.

I think what they did there is pretty cool - I'm sorry if this is the incorrect place to stop but I couldn't find a place to comment in /proposals and I didn't find an appropriate issue here.

@larsth

This comment has been minimized.

Show comment
Hide comment
@larsth

larsth Apr 15, 2016

It could be interesting to have one or more experimental transpilers - a Go generics source code to Go 1.x.y source code compiler.
I mean - too much talk/arguments-for-my-opinion, and no one is writing source code that try to implement some kind of generics for Go.

Just to get knowledge and experience with Go and generics - to see what works and what doesn't work.
If all Go generics solutions aren't really good, then; No generics for Go.

larsth commented Apr 15, 2016

It could be interesting to have one or more experimental transpilers - a Go generics source code to Go 1.x.y source code compiler.
I mean - too much talk/arguments-for-my-opinion, and no one is writing source code that try to implement some kind of generics for Go.

Just to get knowledge and experience with Go and generics - to see what works and what doesn't work.
If all Go generics solutions aren't really good, then; No generics for Go.

@michael-schaller

This comment has been minimized.

Show comment
Hide comment
@michael-schaller

michael-schaller Apr 15, 2016

Contributor

Can the proposal also include the implications on binary size and memory footprint? I would expect that there will be code duplication for each concrete value type so that compiler optimizations work on them. I hope for a guarantee that there will be no code duplication for concrete pointer types.

Contributor

michael-schaller commented Apr 15, 2016

Can the proposal also include the implications on binary size and memory footprint? I would expect that there will be code duplication for each concrete value type so that compiler optimizations work on them. I hope for a guarantee that there will be no code duplication for concrete pointer types.

@mandolyte

This comment has been minimized.

Show comment
Hide comment
@mandolyte

mandolyte Apr 16, 2016

I offer a Pugh Decision matrix. My criteria include perspicuity impacts (source complexity, size). I also forced ranked the criteria to determine the weights for the criteria. Your own may vary of course. I used "interfaces" as the default alternative and compared this to "copy/paste" generics, template based generics (I had in mind something like how D language works), and something I called runtime instantiation style generics. I'm sure this is a vast over simplification. Nonetheless, it may spark some ideas on how to evaluate choices... this should be a public link to my Google Sheet, here

mandolyte commented Apr 16, 2016

I offer a Pugh Decision matrix. My criteria include perspicuity impacts (source complexity, size). I also forced ranked the criteria to determine the weights for the criteria. Your own may vary of course. I used "interfaces" as the default alternative and compared this to "copy/paste" generics, template based generics (I had in mind something like how D language works), and something I called runtime instantiation style generics. I'm sure this is a vast over simplification. Nonetheless, it may spark some ideas on how to evaluate choices... this should be a public link to my Google Sheet, here

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Apr 16, 2016

Pinging @yizhouzhang and @andrewcmyers so they can voice their opinions about genus like generics in Go. It sounds like it could be a good match :)

benjamingr commented Apr 16, 2016

Pinging @yizhouzhang and @andrewcmyers so they can voice their opinions about genus like generics in Go. It sounds like it could be a good match :)

@andrewcmyers

This comment has been minimized.

Show comment
Hide comment
@andrewcmyers

andrewcmyers Apr 16, 2016

The generics design we came up with for Genus has static, modular type checking, does not require predeclaring that types implement some interface, and comes with reasonable performance. I would definitely look at it if you're thinking about generics for Go. It does seem like a good fit from my understanding of Go.

Here is a link to the paper that doesn't require ACM Digital Library access:
http://www.cs.cornell.edu/andru/papers/genus/

The Genus home page is here: http://www.cs.cornell.edu/projects/genus/

We haven't released the compiler publicly yet, but we are planning to do that fairly soon.

Happy to answer any questions people have.

andrewcmyers commented Apr 16, 2016

The generics design we came up with for Genus has static, modular type checking, does not require predeclaring that types implement some interface, and comes with reasonable performance. I would definitely look at it if you're thinking about generics for Go. It does seem like a good fit from my understanding of Go.

Here is a link to the paper that doesn't require ACM Digital Library access:
http://www.cs.cornell.edu/andru/papers/genus/

The Genus home page is here: http://www.cs.cornell.edu/projects/genus/

We haven't released the compiler publicly yet, but we are planning to do that fairly soon.

Happy to answer any questions people have.

@andrewcmyers

This comment has been minimized.

Show comment
Hide comment
@andrewcmyers

andrewcmyers Apr 16, 2016

In terms of @mandolyte's decision matrix, Genus scores a 17, tied for #1. I would add some more criteria to score, though. For example, modular type checking is important, as others such as @sbunce observed above, but template-based schemes lack it. The technical report for the Genus paper has a much larger table on page 34, comparing various generics designs.

andrewcmyers commented Apr 16, 2016

In terms of @mandolyte's decision matrix, Genus scores a 17, tied for #1. I would add some more criteria to score, though. For example, modular type checking is important, as others such as @sbunce observed above, but template-based schemes lack it. The technical report for the Genus paper has a much larger table on page 34, comparing various generics designs.

@andrewcmyers

This comment has been minimized.

Show comment
Hide comment
@andrewcmyers

andrewcmyers Apr 17, 2016

I just went through the whole Summary of Go Generics document, which was a helpful summary of previous discussions. The generics mechanism in Genus does not, to my mind, suffer from the problems identified for C++, Java, or C#. Genus generics are reified, unlike in Java, so you can find out types at run time. You can also instantiate on primitive types, and you don't get implicit boxing in the places you really don't want it: arrays of T where T is a primitive. The type system is closest to Haskell and Rust -- actually a bit more powerful, but I think also intuitive. Primitive specialization ala C# is not currently supported in Genus but it could be. In most cases, specialization can be determined at link time, so true run-time code generation would not be required.

andrewcmyers commented Apr 17, 2016

I just went through the whole Summary of Go Generics document, which was a helpful summary of previous discussions. The generics mechanism in Genus does not, to my mind, suffer from the problems identified for C++, Java, or C#. Genus generics are reified, unlike in Java, so you can find out types at run time. You can also instantiate on primitive types, and you don't get implicit boxing in the places you really don't want it: arrays of T where T is a primitive. The type system is closest to Haskell and Rust -- actually a bit more powerful, but I think also intuitive. Primitive specialization ala C# is not currently supported in Genus but it could be. In most cases, specialization can be determined at link time, so true run-time code generation would not be required.

@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

gopherbot commented Apr 18, 2016

CL https://golang.org/cl/22163 mentions this issue.

mk0x9 pushed a commit to mk0x9/go that referenced this issue Apr 18, 2016

doc: link to iant's generics proposal from the FAQ.
Updates #15292.

Change-Id: I229f66c2a41ae0738225f2ba7a574478f5d6d620
Reviewed-on: https://go-review.googlesource.com/22163
Reviewed-by: Andrew Gerrand <adg@golang.org>

gopherbot pushed a commit that referenced this issue Apr 18, 2016

doc: link to iant's generics proposal from the FAQ.
Updates #15292.

Change-Id: I229f66c2a41ae0738225f2ba7a574478f5d6d620
Reviewed-on: https://go-review.googlesource.com/22163
Reviewed-by: Andrew Gerrand <adg@golang.org>
Reviewed-on: https://go-review.googlesource.com/22166
Reviewed-by: David Symonds <dsymonds@golang.org>
@jba

This comment has been minimized.

Show comment
Hide comment
@jba

jba Apr 18, 2016

A way to constrain generic types that doesn't require adding new language concepts: https://docs.google.com/document/d/1rX4huWffJ0y1ZjqEpPrDy-kk-m9zWfatgCluGRBQveQ/edit?usp=sharing.

jba commented Apr 18, 2016

A way to constrain generic types that doesn't require adding new language concepts: https://docs.google.com/document/d/1rX4huWffJ0y1ZjqEpPrDy-kk-m9zWfatgCluGRBQveQ/edit?usp=sharing.

@jimmyfrasche

This comment has been minimized.

Show comment
Hide comment
@jimmyfrasche

jimmyfrasche Apr 18, 2016

Member

Genus looks really cool and it's clearly an important advancement of the art, but I don't see how it would apply to Go. Does anyone have a sketch of how it would integrate with the Go type system/philosophy?

Member

jimmyfrasche commented Apr 18, 2016

Genus looks really cool and it's clearly an important advancement of the art, but I don't see how it would apply to Go. Does anyone have a sketch of how it would integrate with the Go type system/philosophy?

@sprstnd

This comment has been minimized.

Show comment
Hide comment
@sprstnd

sprstnd Apr 27, 2016

The issue is the go team is stonewalling attempts. The title clearly states the intentions of the go team. And if that wasn't enough to deter all takers, the features demanded of such a broad domain in the proposals by ian make it clear that if you want generics then they don't want you. It is asinine to even attempt dialog with the go team. To those looking for generics in go, I say fracture the language. Begin a new journey- many will follow. I've already seen some great work done in forks. Organize yourselves, rally around a cause

sprstnd commented Apr 27, 2016

The issue is the go team is stonewalling attempts. The title clearly states the intentions of the go team. And if that wasn't enough to deter all takers, the features demanded of such a broad domain in the proposals by ian make it clear that if you want generics then they don't want you. It is asinine to even attempt dialog with the go team. To those looking for generics in go, I say fracture the language. Begin a new journey- many will follow. I've already seen some great work done in forks. Organize yourselves, rally around a cause

@andrewcmyers

This comment has been minimized.

Show comment
Hide comment
@andrewcmyers

andrewcmyers Apr 27, 2016

If anyone wants to try to work up a generics extension to Go based on the Genus design, we are happy to help. We don't know Go well enough to produce a design that harmonizes with the existing language. I think the first step would be a straw-man design proposal with worked-out examples.

andrewcmyers commented Apr 27, 2016

If anyone wants to try to work up a generics extension to Go based on the Genus design, we are happy to help. We don't know Go well enough to produce a design that harmonizes with the existing language. I think the first step would be a straw-man design proposal with worked-out examples.

@mandolyte

This comment has been minimized.

Show comment
Hide comment
@mandolyte

mandolyte Apr 28, 2016

@andrewcmyers hoping that @ianlancetaylor will work with you on that. Just having some examples to look at would help a lot.

mandolyte commented Apr 28, 2016

@andrewcmyers hoping that @ianlancetaylor will work with you on that. Just having some examples to look at would help a lot.

@yookoala

This comment has been minimized.

Show comment
Hide comment
@yookoala

yookoala Sep 14, 2018

If you didn't know, the Go Team has announced a draft of Go 2.0:
https://golang.org/s/go2designs

There is a draft to the Generics design in Go 2.0 (contract). You may want to take a look and give feedback on their Wiki.

This the relevant section:

Generics

yookoala commented Sep 14, 2018

If you didn't know, the Go Team has announced a draft of Go 2.0:
https://golang.org/s/go2designs

There is a draft to the Generics design in Go 2.0 (contract). You may want to take a look and give feedback on their Wiki.

This the relevant section:

Generics

@luismasuelli-jobsity

This comment has been minimized.

Show comment
Hide comment
@luismasuelli-jobsity

luismasuelli-jobsity Sep 17, 2018

luismasuelli-jobsity commented Sep 17, 2018

@luismasuelli-jobsity

This comment has been minimized.

Show comment
Hide comment
@luismasuelli-jobsity

luismasuelli-jobsity Sep 17, 2018

luismasuelli-jobsity commented Sep 17, 2018

@keean

This comment has been minimized.

Show comment
Hide comment
@keean

keean Sep 17, 2018

@luismasuelli-jobsity If I read the history of generic implementations in Go correctly, then it looks like the reason to introduce Contracts is because they did not want operator overloading in Interfaces.

An earlier proposal that was eventually rejected used interfaces to constrain parametric polymorphism, but seems to have been rejected because you could not use common operators like '+' in such functions because it is not definable in an interface. Contracts allow you to write t == t or t + t so you can indicate the type must support equality or addition etc.

Edit: Also Go does not support multiple type parameter interfaces, so in a way Go has separated typeclass into two separate things, Contracts that relate the functions type parameters to each other, and interfaces which supply methods. What it looses is the ability to select a typeclass implementation based on multiple types. It is arguably simpler if you only need to use interfaces or contracts, but more complex if you need to use both together.

keean commented Sep 17, 2018

@luismasuelli-jobsity If I read the history of generic implementations in Go correctly, then it looks like the reason to introduce Contracts is because they did not want operator overloading in Interfaces.

An earlier proposal that was eventually rejected used interfaces to constrain parametric polymorphism, but seems to have been rejected because you could not use common operators like '+' in such functions because it is not definable in an interface. Contracts allow you to write t == t or t + t so you can indicate the type must support equality or addition etc.

Edit: Also Go does not support multiple type parameter interfaces, so in a way Go has separated typeclass into two separate things, Contracts that relate the functions type parameters to each other, and interfaces which supply methods. What it looses is the ability to select a typeclass implementation based on multiple types. It is arguably simpler if you only need to use interfaces or contracts, but more complex if you need to use both together.

@DeedleFake

This comment has been minimized.

Show comment
Hide comment
@DeedleFake

DeedleFake Sep 17, 2018

Why T:Addable means "a type T implementing the contract Addable"?

That's actually not what it means; it just looks that way for one type argument. Elsewhere in the draft it makes the comment that you can only have one contract per function, and this is where the main difference comes in. Contracts are actually statements about the types of the function, not just the types independently. For example, if you have

func Example(type K, V someContract)(k K, v V) V

you can do something like

contract someContract(k K, v V) {
  k.someMethod(v)
}

This vastly simplifies coordinating multiple types without having to redundantly specify the types in the function signature. Remember, they're trying to avoid the 'curiously repeating generic pattern'. For example, the same function with parameterized interfaces used to constrain the types would be something like

type someMethoder(V) interface {
  someMethod(V)
}

func Example(type K: someMethoder(V), V)(k K, v V) V

This is kind of awkward. The contract syntax allows you to still do this if you need to, though, because the contract's 'arguments' are auto-filled by the compiler if the contract has the same number of them as the function does type parameters. You can specify them manually if you want to, though, meaning that you could do func Example(type K, V someContract(K, V))(k K, v V) V if you really wanted to, though it's not particularly useful in this situation.

One way of making it clearer that contracts are about entire functions, not individual arguments, would be to simply associate them based on name. For example,

contract Example(k K, v V) {
  k.someMethod(v)
}

func Example(type K, V)(k K, v V) V

would be the same as the above. The downside, however, is that contracts would not be re-usable and you lose that ability to specify the contract's arguments manually.

Edit: To show further why they want to solve the curiously repeating pattern, consider the shortest path problem that they kept referring to. With parameterized interfaces, the definition winds up looking like

type E(Node) interface {
  Nodes() []Node
}

type N(Edge) interface {
  Edges() (from, to Edge)
}

type Graph(type Node: N(Edge), Edge: E(Node)) struct { ... }
func New(type Node: N(Edge), Edge: E(Node))(nodes []Node) *Graph(Node, Edge) { ... }
func (*Graph(Node, Edge)) ShortestPath(from, to Node) []Edge { ... }

DeedleFake commented Sep 17, 2018

Why T:Addable means "a type T implementing the contract Addable"?

That's actually not what it means; it just looks that way for one type argument. Elsewhere in the draft it makes the comment that you can only have one contract per function, and this is where the main difference comes in. Contracts are actually statements about the types of the function, not just the types independently. For example, if you have

func Example(type K, V someContract)(k K, v V) V

you can do something like

contract someContract(k K, v V) {
  k.someMethod(v)
}

This vastly simplifies coordinating multiple types without having to redundantly specify the types in the function signature. Remember, they're trying to avoid the 'curiously repeating generic pattern'. For example, the same function with parameterized interfaces used to constrain the types would be something like

type someMethoder(V) interface {
  someMethod(V)
}

func Example(type K: someMethoder(V), V)(k K, v V) V

This is kind of awkward. The contract syntax allows you to still do this if you need to, though, because the contract's 'arguments' are auto-filled by the compiler if the contract has the same number of them as the function does type parameters. You can specify them manually if you want to, though, meaning that you could do func Example(type K, V someContract(K, V))(k K, v V) V if you really wanted to, though it's not particularly useful in this situation.

One way of making it clearer that contracts are about entire functions, not individual arguments, would be to simply associate them based on name. For example,

contract Example(k K, v V) {
  k.someMethod(v)
}

func Example(type K, V)(k K, v V) V

would be the same as the above. The downside, however, is that contracts would not be re-usable and you lose that ability to specify the contract's arguments manually.

Edit: To show further why they want to solve the curiously repeating pattern, consider the shortest path problem that they kept referring to. With parameterized interfaces, the definition winds up looking like

type E(Node) interface {
  Nodes() []Node
}

type N(Edge) interface {
  Edges() (from, to Edge)
}

type Graph(type Node: N(Edge), Edge: E(Node)) struct { ... }
func New(type Node: N(Edge), Edge: E(Node))(nodes []Node) *Graph(Node, Edge) { ... }
func (*Graph(Node, Edge)) ShortestPath(from, to Node) []Edge { ... }
@DeedleFake

This comment has been minimized.

Show comment
Hide comment
@DeedleFake

DeedleFake Sep 17, 2018

Personally, I rather like the way that contracts are specified for functions. I'm not too keen on just having 'normal' function bodies as the actual contract specification, but I think a lot of the potential problems could be solved by introducing some kind of gofmt-like simplifier that auto-simplifies contracts for you, removing extraneous parts. Then you could just copy a function body into it, simplify it, and modify it from there. I'm not sure how possible this will be to implement, though, unfortunately.

Some things will still be a bit awkward to specify, though, and the apparent overlap between contracts and interfaces still seems a bit odd.

DeedleFake commented Sep 17, 2018

Personally, I rather like the way that contracts are specified for functions. I'm not too keen on just having 'normal' function bodies as the actual contract specification, but I think a lot of the potential problems could be solved by introducing some kind of gofmt-like simplifier that auto-simplifies contracts for you, removing extraneous parts. Then you could just copy a function body into it, simplify it, and modify it from there. I'm not sure how possible this will be to implement, though, unfortunately.

Some things will still be a bit awkward to specify, though, and the apparent overlap between contracts and interfaces still seems a bit odd.

@jimmyfrasche

This comment has been minimized.

Show comment
Hide comment
@jimmyfrasche

jimmyfrasche Sep 17, 2018

Member

I find the "CRTP" version much clearer, more explicit, and easier to work with (no need to create contracts that only exist to define the relationship between pre-existing contracts over a set of variables). Admittedly, that could just be the many years of familiarity with the idea.

Member

jimmyfrasche commented Sep 17, 2018

I find the "CRTP" version much clearer, more explicit, and easier to work with (no need to create contracts that only exist to define the relationship between pre-existing contracts over a set of variables). Admittedly, that could just be the many years of familiarity with the idea.

@yookoala

This comment has been minimized.

Show comment
Hide comment
@yookoala

yookoala Sep 19, 2018

Clarifications. By the draft design, contract can be applied to both functions and types.

yookoala commented Sep 19, 2018

Clarifications. By the draft design, contract can be applied to both functions and types.

@luismasuelli-jobsity

This comment has been minimized.

Show comment
Hide comment
@luismasuelli-jobsity

luismasuelli-jobsity Sep 19, 2018

"""
It is arguably simpler if you only need to use interfaces or contracts, but more complex if you need to use both together.
"""

As long as they allow you to, inside a contract, reference one or more interfaces (instead of only operators and functions, thus allowing DRY), this issue (and my claim) will be solved. There is a chance that I misread or did not completely read the contracts stuff, and also a chance that the said feature is supported and I did not notice it. If it isn't, it should be.

luismasuelli-jobsity commented Sep 19, 2018

"""
It is arguably simpler if you only need to use interfaces or contracts, but more complex if you need to use both together.
"""

As long as they allow you to, inside a contract, reference one or more interfaces (instead of only operators and functions, thus allowing DRY), this issue (and my claim) will be solved. There is a chance that I misread or did not completely read the contracts stuff, and also a chance that the said feature is supported and I did not notice it. If it isn't, it should be.

@DeedleFake

This comment has been minimized.

Show comment
Hide comment
@DeedleFake

DeedleFake Sep 20, 2018

Can't you do the following?

contract Example(t T, v V) {
  t.(interface{
    SomeMethod() V
  })
}

You can't use an interface that's declared elsewhere because of the restriction that you can't reference identifiers from the same package as the contract is declared in, but you can do this. Or they could just remove that restriction; it seems a bit arbitrary.

DeedleFake commented Sep 20, 2018

Can't you do the following?

contract Example(t T, v V) {
  t.(interface{
    SomeMethod() V
  })
}

You can't use an interface that's declared elsewhere because of the restriction that you can't reference identifiers from the same package as the contract is declared in, but you can do this. Or they could just remove that restriction; it seems a bit arbitrary.

@Merovius

This comment has been minimized.

Show comment
Hide comment
@Merovius

Merovius Sep 20, 2018

@DeedleFake No, because any interface type can be type-asserted (and then just potentially panic at runtime, but contracts aren't executed). But you can use an assignment instead.

Merovius commented Sep 20, 2018

@DeedleFake No, because any interface type can be type-asserted (and then just potentially panic at runtime, but contracts aren't executed). But you can use an assignment instead.

@jimmyfrasche

This comment has been minimized.

Show comment
Hide comment
@jimmyfrasche

jimmyfrasche Sep 20, 2018

Member

t.(someInterface) would also mean that it must be an interface

Member

jimmyfrasche commented Sep 20, 2018

t.(someInterface) would also mean that it must be an interface

@DeedleFake

This comment has been minimized.

Show comment
Hide comment
@DeedleFake

DeedleFake Sep 20, 2018

Good point. Woops.

The more examples of this I see, the more error-prone 'figure it out from a function body' seems to be.

DeedleFake commented Sep 20, 2018

Good point. Woops.

The more examples of this I see, the more error-prone 'figure it out from a function body' seems to be.

@jimmyfrasche

This comment has been minimized.

Show comment
Hide comment
@jimmyfrasche

jimmyfrasche Sep 20, 2018

Member

There are lots of cases where it's confusing for a person, same syntax for different operations, shades of implications from different constructs, etc., but a tool would be able to take that and reduce it to a normal form. But then the output of such a tool becomes a de facto sub-language for expressing type constraints that we have to learn by rote, making it all the more surprising when someone deviates and writes a contract by hand.

I'll also note that

contract I(t T) {
  var i interface { Foo() }
  i = t
  t.(interface{})
}

expresses that T must be an interface with at least Foo() but it could also have any other number of additional methods.

Member

jimmyfrasche commented Sep 20, 2018

There are lots of cases where it's confusing for a person, same syntax for different operations, shades of implications from different constructs, etc., but a tool would be able to take that and reduce it to a normal form. But then the output of such a tool becomes a de facto sub-language for expressing type constraints that we have to learn by rote, making it all the more surprising when someone deviates and writes a contract by hand.

I'll also note that

contract I(t T) {
  var i interface { Foo() }
  i = t
  t.(interface{})
}

expresses that T must be an interface with at least Foo() but it could also have any other number of additional methods.

@DeedleFake

This comment has been minimized.

Show comment
Hide comment
@DeedleFake

DeedleFake Sep 20, 2018

T must be an interface with at least Foo() but it could also have any other number of additional methods

Is that a problem, though? Don't you usually want to constrain things so that they allow specific functionality but you don't care about other functionality? Otherwise a contract like

contract Example(t T) {
  t + t
}

wouldn't allow subtraction, for example. But from the point of view whatever I'm implementing, I don't care if a type allows subtraction or not. If I restricted it from being able to perform subtraction, then people would just arbitrarily not be able, to for example, pass anything that does to a Sum() function or something. That seems arbitrarily restrictive.

DeedleFake commented Sep 20, 2018

T must be an interface with at least Foo() but it could also have any other number of additional methods

Is that a problem, though? Don't you usually want to constrain things so that they allow specific functionality but you don't care about other functionality? Otherwise a contract like

contract Example(t T) {
  t + t
}

wouldn't allow subtraction, for example. But from the point of view whatever I'm implementing, I don't care if a type allows subtraction or not. If I restricted it from being able to perform subtraction, then people would just arbitrarily not be able, to for example, pass anything that does to a Sum() function or something. That seems arbitrarily restrictive.

@jimmyfrasche

This comment has been minimized.

Show comment
Hide comment
@jimmyfrasche

jimmyfrasche Sep 20, 2018

Member

No, it's not a problem at all. It was just an unintuitive (to me) property, but perhaps that was due to insufficient coffee.

Member

jimmyfrasche commented Sep 20, 2018

No, it's not a problem at all. It was just an unintuitive (to me) property, but perhaps that was due to insufficient coffee.

@yookoala

This comment has been minimized.

Show comment
Hide comment
@yookoala

yookoala Sep 21, 2018

Its fair to say that the current contract declaration need to have better compiler messages to work with. And the rules for a valid contract should be strict.

yookoala commented Sep 21, 2018

Its fair to say that the current contract declaration need to have better compiler messages to work with. And the rules for a valid contract should be strict.

@surlykke

This comment has been minimized.

Show comment
Hide comment
@surlykke

surlykke Sep 30, 2018

Hi
I made a proposal for constraints for generics that i posted in this thread about ½ a year ago.
Now I've made a version 2. The main changes are:

  • The syntax has been adapted to the one proposed by the go-team.
  • Constraining by fields have been omitted, which allows for quite a bit of simplifications.
  • Paragraphs deemed not strictly necessary has been taken out.

surlykke commented Sep 30, 2018

Hi
I made a proposal for constraints for generics that i posted in this thread about ½ a year ago.
Now I've made a version 2. The main changes are:

  • The syntax has been adapted to the one proposed by the go-team.
  • Constraining by fields have been omitted, which allows for quite a bit of simplifications.
  • Paragraphs deemed not strictly necessary has been taken out.
@Merovius

This comment has been minimized.

Show comment
Hide comment
@Merovius

Merovius Oct 8, 2018

I thought of an interesting (but maybe more detailed than appropriate at this stage in the design?) question regarding type identity recently:

func Foo() interface{} {
    type S struct {}
    return S{}
}

func Bar(type T)() interface{} {
    type S struct {}
    return S{}
}

func Baz(type T)() interface{} {
    type S struct{t T}
    return S{}
}

func main() {
    fmt.Println(Foo() == Foo()) // 1
    fmt.Println(Bar(int)() == Bar(string)()) // 2
    fmt.Println(Baz(int)() == Baz(string)()) // 3
}
  1. Prints true, because the types of the returned values are originating in the same type declaration.
  2. Prints…?
  3. Prints false, I assume.

i.e. the question is, when two types declared in a generic function are identical and when they aren't. I don't think this is described in the spec design? At least I can't find it right now :)

Merovius commented Oct 8, 2018

I thought of an interesting (but maybe more detailed than appropriate at this stage in the design?) question regarding type identity recently:

func Foo() interface{} {
    type S struct {}
    return S{}
}

func Bar(type T)() interface{} {
    type S struct {}
    return S{}
}

func Baz(type T)() interface{} {
    type S struct{t T}
    return S{}
}

func main() {
    fmt.Println(Foo() == Foo()) // 1
    fmt.Println(Bar(int)() == Bar(string)()) // 2
    fmt.Println(Baz(int)() == Baz(string)()) // 3
}
  1. Prints true, because the types of the returned values are originating in the same type declaration.
  2. Prints…?
  3. Prints false, I assume.

i.e. the question is, when two types declared in a generic function are identical and when they aren't. I don't think this is described in the spec design? At least I can't find it right now :)

@keean

This comment has been minimized.

Show comment
Hide comment
@keean

keean Oct 9, 2018

@Merovius I assume the middle case was supposed to be:

fmt.Println(Bar(int)() == Bar(int)()) // 2

This is an interesting case, and it depends on whether types are "generative" or "applicative". There are actually to variants of ML that take different approaches. Applicative types view the generic as a type function, and hence f(int) == f(int). Generative types view the generic as a type template that creates a new unique 'instance' type each time it is used so t<int> != t<int>. This must be approached at a whole type-system level as it has subtle implications for unification, inference and soundness. For further details and examples of then kind of problems I recommend reading Andreas Rossberg's "F-ing modules" paper: https://people.mpi-sws.org/~rossberg/f-ing/ although the paper is talking about ML "functors" this is because ML separates its type system into two levels, and functors are MLs equivalent of a generic and are only available at the module level.

keean commented Oct 9, 2018

@Merovius I assume the middle case was supposed to be:

fmt.Println(Bar(int)() == Bar(int)()) // 2

This is an interesting case, and it depends on whether types are "generative" or "applicative". There are actually to variants of ML that take different approaches. Applicative types view the generic as a type function, and hence f(int) == f(int). Generative types view the generic as a type template that creates a new unique 'instance' type each time it is used so t<int> != t<int>. This must be approached at a whole type-system level as it has subtle implications for unification, inference and soundness. For further details and examples of then kind of problems I recommend reading Andreas Rossberg's "F-ing modules" paper: https://people.mpi-sws.org/~rossberg/f-ing/ although the paper is talking about ML "functors" this is because ML separates its type system into two levels, and functors are MLs equivalent of a generic and are only available at the module level.

@Merovius

This comment has been minimized.

Show comment
Hide comment
@Merovius

Merovius Oct 9, 2018

@keean You assume wrong.

Merovius commented Oct 9, 2018

@keean You assume wrong.

@keean

This comment has been minimized.

Show comment
Hide comment
@keean

keean Oct 9, 2018

@Merovius Yes, my mistake, I see the question is because the type parameter is not used (a phantom type).

With generative types, each instantiation would result in a different unique type for 'S', so even though the parameter is not used, they would not be equal.

With applicative types, the 'S' from each instantiation would be the same type, and so they would be equal.

keean commented Oct 9, 2018

@Merovius Yes, my mistake, I see the question is because the type parameter is not used (a phantom type).

With generative types, each instantiation would result in a different unique type for 'S', so even though the parameter is not used, they would not be equal.

With applicative types, the 'S' from each instantiation would be the same type, and so they would be equal.

@carlmjohnson

This comment has been minimized.

Show comment
Hide comment
@carlmjohnson

carlmjohnson Oct 9, 2018

Contributor

It would be weird if the result in case 2 changed based on compiler optimizations. Sounds like UB.

Contributor

carlmjohnson commented Oct 9, 2018

It would be weird if the result in case 2 changed based on compiler optimizations. Sounds like UB.

@dataf3l

This comment has been minimized.

Show comment
Hide comment
@dataf3l

dataf3l Oct 20, 2018

It's 2018 people, I can't believe I actually have to type this like in 1982:

func min(x, y int) int {
if x < y {
return x
}
return y
}

func max(x, y int) int {
if x > y {
return x
}
return y
}

I mean, seriously, dudes MIN(INT,INT) INT, how is that NOT in the language?
I'm angry.

dataf3l commented Oct 20, 2018

It's 2018 people, I can't believe I actually have to type this like in 1982:

func min(x, y int) int {
if x < y {
return x
}
return y
}

func max(x, y int) int {
if x > y {
return x
}
return y
}

I mean, seriously, dudes MIN(INT,INT) INT, how is that NOT in the language?
I'm angry.

@keean

This comment has been minimized.

Show comment
Hide comment
@keean

keean Oct 20, 2018

@dataf3l If you want those to work as expected with pre-orders then:

func min(x, y int) int {
   if x <= y {
      return x
   }
   return y
}

This is so the pair (min(x, y), max(x, y)) is always distinct and is either (x, y) or (y, x), and that is therefore a stable sort of two elements.

So another reason these should be in the language or a library is that people mostly get them wrong :-)

keean commented Oct 20, 2018

@dataf3l If you want those to work as expected with pre-orders then:

func min(x, y int) int {
   if x <= y {
      return x
   }
   return y
}

This is so the pair (min(x, y), max(x, y)) is always distinct and is either (x, y) or (y, x), and that is therefore a stable sort of two elements.

So another reason these should be in the language or a library is that people mostly get them wrong :-)

@dataf3l

This comment has been minimized.

Show comment
Hide comment
@dataf3l

dataf3l Oct 20, 2018

I thought about the < vs <=, for integers, I'm not sure I quite see the difference.
Maybe I'm just dumb...

dataf3l commented Oct 20, 2018

I thought about the < vs <=, for integers, I'm not sure I quite see the difference.
Maybe I'm just dumb...

@cznic

This comment has been minimized.

Show comment
Hide comment
@cznic

cznic Oct 20, 2018

Contributor

I'm not sure I quite see the difference.

There's none in this case.

Contributor

cznic commented Oct 20, 2018

I'm not sure I quite see the difference.

There's none in this case.

@keean

This comment has been minimized.

Show comment
Hide comment
@keean

keean Oct 20, 2018

@cznic true in this case as they are integers, however as the thread was about generics I assumed that the library comment was about having generic definitions of min and max so users don't have to declare them themselves. Re-reading the OP I can see they just want simple min and max for integers, so my bad, but they were off topic asking for simple integration functions in a thread about generics :-)

keean commented Oct 20, 2018

@cznic true in this case as they are integers, however as the thread was about generics I assumed that the library comment was about having generic definitions of min and max so users don't have to declare them themselves. Re-reading the OP I can see they just want simple min and max for integers, so my bad, but they were off topic asking for simple integration functions in a thread about generics :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment