Skip to content
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: Go 2: generics using const struct fields #30518

Closed
HALtheWise opened this issue Mar 1, 2019 · 8 comments
Closed

proposal: Go 2: generics using const struct fields #30518

HALtheWise opened this issue Mar 1, 2019 · 8 comments
Labels
FrozenDueToAge generics Issue is related to generics LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@HALtheWise
Copy link

HALtheWise commented Mar 1, 2019

I ended up writing and refining a design-document-like-thing for an approach to generics that lives at the intersection of the existing go features of structs and constants in order to more naturally integrate into the language while remaining orthogonal to current features. Any and all suggestions are welcome.

go2 proposal: simple generics using const struct fields

To summarize, the basic idea is to allow struct type declarations to include const and const type fields, which can be referenced inside their methods to allow generic code. These fields must be filled at the time of variable declaration in a way that allows the compiler and tools to know the concrete signatures of their methods at the callsite.

@gopherbot gopherbot added this to the Proposal milestone Mar 1, 2019
@HALtheWise HALtheWise changed the title Proposal: generics using const struct fields (Go 2) Proposal: Go2: generics using const struct fields Mar 1, 2019
@ianlancetaylor ianlancetaylor changed the title Proposal: Go2: generics using const struct fields proposal: Go 2: generics using const struct fields Mar 1, 2019
@ianlancetaylor ianlancetaylor added LanguageChange v2 A language change or incompatible library change labels Mar 1, 2019
@dpinela
Copy link
Contributor

dpinela commented Mar 1, 2019

This proposal would not allow any generic functions (except for methods on generic struct types), or generic non-struct types. That goes against the principle of orthogonality which Go seeks to follow. There's probably more problems, but this is a big one.

@HALtheWise
Copy link
Author

Interesting observation, I have always thought about orthogonality as being mostly a function of preventing there from being two different ways of accomplishing the same task, rather than making sure that as many permutations of features are usable together as possible. I am curious how important you think it is to have compiletime parameterization based on anything other than types, like integers, first-class functions, or other go types. I considered for a while a simpler version that just allowed type fields, rather than arbitrary const fields, but the idea of a sort function with a custom compare function that could be inlined at compile time (const func) was really attractive, not to mention binary size improvements from const config structs or other such cases.

One primary goal I was trying to accomplish was to follow the principle of least surprise, ideally to the point of making functionality that someone learning the language today would expect to work already, and be surprised does not exist. Contracts don't feel to me like they rise to that level of intuitiveness, but "fields of structs can be type constants" does. I'm curious what your thoughts are.

I think the lack of support for generic standalone functions is not that big of a deal in real large-scale programs, although it looks significant on tiny examples. Especially with the pattern of size-0 structs acting as user-declared "packages" of functions, I'm OK with there being a little more typing required to be explicit about using generic functions, and sort.Sorter{int}.Sort(l) isn't actually that much longer than sort.Sort<int>(l).

@beoran
Copy link

beoran commented Mar 5, 2019

Interesting proposal. Why not also allow const type also in parameters to functions, to get generic functions also? And for the contraints of cont type, interfaces should also be allowed in the constraints block after them.

@HALtheWise
Copy link
Author

@beoran The original intent behind not allowing const arguments to functions was to separate the place where you feed the types into a generic function from the place where you call it. This makes tooling (including compiler messages) simpler, because any function will always have a specific concrete type at the time it is called, and figuring out a valid set of arguments for it doesn't risk becoming a complicated task. It is definitely a little bit verbose, but it felt to me like a little more keyboard-typing would be better than a little more complexity in this case. I'm curious to hear your thoughts, particularly around the pseudo-package pattern used in the sort example. If you have an example where a generic function (not a method) is actually needed, that could be interesting as well.

Regarding constraint blocks, interfaces would definately be allowed and encouraged in those blocks, both for simple cases and more complicated things like the graphutils example, where the interface constraint actually changes based on previous const types.

@beoran
Copy link

beoran commented Mar 15, 2019

I have seen many Go Generics proposals, and while this has some good ideas, I think it ends up being less powerful than it could be. I implemented something similar to this proposal, using text/template to generate Go files. Having types as parameters to functions for generic functions, like in may other generics proposals seems to be a more promising and powerful approach.

@HALtheWise
Copy link
Author

@beoran
Those are good points, although I am skeptical a template-based system can guarantee that errors are always detectable by the library author.

I think we should be careful to not just make generics maximally powerful, in a similar way to the fact that interfaces in Go "only" support methods, not fields or any other properties of types. It requires some more work from the programmer on occasion (like making getters), and is arguably less powerful than an interface syntax that also allowed direct specification of fields, but is also simpler, which is important. I worry that many/all of the more powerful generics systems I have seen proposed are also, by their very nature, hard to reason about what types can be used where (or turing-complete in the worst case). Much like how Go modules are intentionally limited compared to something like dep, I think we need a slightly more limiting generic system at the benefit of significantly simpler behavior.

If you are aware of other proposals that fall into that bucket (or that otherwise disallow confusing and complicated type signatures), I would love to hear about them!

@HALtheWise
Copy link
Author

For the sake of having all the discussion in one place, I'm copying here a conversation with @ianlancetaylor from the golang-dev mailing list.


Thanks.
I don't understand how to write an Index function, which takes a slice of some element type and a value of that element type. In the design draft:

func Index(type T)(s []T, v T) int

How do I write a function that takes two values of related but not identical types? Are you also suggesting that we extend parameter types to permit them to be elements of previously specified
parameters?

Ian


This proposal intentionally does not support generic functions that are not methods, so the answer is to make a type of a 0-size "Indexer" type that is parameterized off of the element type, and has an Index method that does what you want. This adds a little bit of code overhead, but makes it more explicit when you are instantiating a bunch of generic functions. In practice, you would probably make a WrappedSlice type that has helpers for several things, not just Index, decreasing the overhead (like example 3 in the doc).

type Indexer struct{
   T const type{types.Equality}
}

func (i Indexer) Index(s []i.T, v i.T)int{}

Used as either...

intIndexer := utils.Indexer{int}
//...
intIndexer.Index(...)

// -- or --
utils.Indexer{int}.Index(...)

Effectively, force the user of the method to explicitly specify the leaf types ("int"), then build the type signature of the method from those.
In this proposal, parameter or return types can specifically only be elements of the method receiver, which I think is a simpler mental model than generally allowing them to be methods of any previous parameter.


I have also updated the document with a brief bullet point about this issue, since it has come up multiple times.

@ianlancetaylor
Copy link
Contributor

ianlancetaylor commented Mar 26, 2019

Let's see how the current generics design draft, which does permit generic functions, plays out. If that does not get adopted, we can return to this idea.

@ianlancetaylor ianlancetaylor added the generics Issue is related to generics label Oct 29, 2019
@golang golang locked and limited conversation to collaborators Oct 28, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge generics Issue is related to generics LanguageChange Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

5 participants