-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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: type intersection and composition #71641
Comments
Related Issues
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.) |
This proposal doesn't answer any of the interesting questions as to why existing embedding isn't sufficient, or how it'd be different. Rather than try to intersect fields, it would probably be better to just define common struct and embed that into the places that need them. While this proposal has some syntax, there doesn't appear to be anything real to evaluate. |
I'm sorry, but the type concatenation feature is not as small as you make it out to be. For most developers, this kind of feature will help them write better quality and more comfortable code. Below I share a few examples of areas where it can be used Current usage type UserDepartmentsRK struct {
UserId types.URID `json:"userId" gorm:"primaryKey;column:user_id;type:uuid;"`
CompanyId types.URID `json:"companyId" gorm:"primaryKey;column:company_id;type:uuid;"`
DepartmentId types.ID `json:"departmentId" gorm:"primaryKey;column:department_id"`
}
type UserDepartmentsForm struct {
UserId types.URID `json:"userId" gorm:"primaryKey;column:user_id;type:uuid;"`
CompanyId types.URID `json:"companyId" gorm:"primaryKey;column:company_id;type:uuid;"`
DepartmentId types.ID `json:"departmentId" gorm:"primaryKey;column:department_id"`
Supervisor *bool `gorm:"column:supervisor;nullable" json:"supervisor,omitempty" validate:"omitempty" swaggertype:"boolean" example:"true" `
TimeCreated types.NullTime `gorm:"column:time_created;nullable;autoCreateTime" json:"timeCreated,omitempty" validate:"omitempty" swaggertype:"string" example:"1960-09-17T02:37:51Z" format:"date-time"`
} Planned usage type UserDepartmentsRK struct {
UserId types.URID `json:"userId" gorm:"primaryKey;column:user_id;type:uuid;"`
CompanyId types.URID `json:"companyId" gorm:"primaryKey;column:company_id;type:uuid;"`
DepartmentId types.ID `json:"departmentId" gorm:"primaryKey;column:department_id"`
}
type UserDepartmentsForm struct UserDepartmentsRK & {
Supervisor *bool `gorm:"column:supervisor;nullable" json:"supervisor,omitempty" validate:"omitempty" swaggertype:"boolean" example:"true" `
TimeCreated types.NullTime `gorm:"column:time_created;nullable;autoCreateTime" json:"timeCreated,omitempty" validate:"omitempty" swaggertype:"string" example:"1960-09-17T02:37:51Z" format:"date-time"`
} |
With the "inline" option in #63397, you should be able to do that with embedding. |
Go Programming Experience
Experienced
Other Languages Experience
Typescript
Related Idea
Has this idea, or one like it, been proposed before?
No
Does this affect error handling?
No
Is this about generics?
No
Proposal
This proposal suggests adding a mechanism to Go's type system that allows developers to define new types by combining existing types. This could be achieved in one of two primary ways:
Type Intersection: A new type would be created that possesses all the fields and methods of the constituent types. This would be analogous to intersection types in TypeScript or similar languages. For example:
Go
type Address struct { Street string; City string }
type Person struct { Name string; Age int }
type UserInfo = Address & Person // Proposed syntax
UserInfo would then have the fields Street, City, Name, and Age.
Type Composition (or Embedded Structs 2.0): This would build upon Go's existing embedding mechanism but potentially offer more explicit control. It might involve a new keyword or syntax to clearly indicate the intent of composing types. While embedding already allows for composition, a more explicit approach could address some of its current limitations (e.g., name clashes). An example (using a hypothetical composes keyword):
This, similar to the intersection approach, would result in UserInfo having the fields Street, City, Name, and Age. The key difference would lie in how name collisions are handled and potentially in how methods are promoted.
Who does this proposal help, and why?
This proposal would benefit Go developers in several ways:
Reduced Code Duplication: Currently, if you need a type that combines fields from multiple structs, you often have to manually copy those fields into a new struct definition. This leads to redundant code, which is harder to maintain and prone to errors. Type intersection/composition would eliminate this duplication.
Improved Code Readability: The intent of combining types would be expressed directly in the type declaration, making the code easier to understand and reason about.
Enhanced Type Safety: By explicitly defining the combined type, the compiler could enforce type correctness, preventing common errors related to missing or mismatched fields.
More Expressive Type System: The addition of type intersection or composition would make Go's type system more powerful and flexible, allowing developers to model complex data structures more effectively.
This would be particularly helpful in scenarios involving:
Data Modeling: Representing complex entities that are composed of multiple, simpler entities (e.g., a customer with an address, contact information, and purchase history).
API Design: Creating flexible and reusable data structures for APIs.
Code Reusability: Combining existing types to create new types without rewriting code.
Please describe as precisely as possible the change to the language.
Language Spec Changes
Adding type intersection or composition would necessitate changes to the Go Language Specification in several key areas:
Type Declarations: The most obvious change would be the introduction of new syntax within type declarations. This could involve:
Intersection: Introducing the & operator within type declarations to signify intersection. For example:
Composition: Introducing a new keyword (e.g., composes) or modifying the existing embedding syntax to explicitly denote composition. For example:
Type Identity and Assignability: The spec would need to define how the new composite types are treated in terms of type identity and assignability. Specifically:
When are two composite types considered equal? (e.g., same constituent types, same order, etc.)
What are the rules for assigning values of one type to another involving the new composite types?
Field and Method Promotion: The spec would need to detail how fields and methods from the constituent types are "promoted" to the new composite type. This would include:
How are name collisions handled? (e.g., if both Address and Person have a String() method)
Which methods are accessible on the composite type? (e.g., all from both, or only those from one?)
Scope and Visibility: The spec might need to clarify how the scope and visibility of fields and methods are affected by the new composite type.
Embedding (If Applicable): If the composition approach builds upon embedding, the spec might need adjustments to reflect the new keyword/syntax and any resulting changes in behavior compared to current embedding.
Compiler and Tooling: While not strictly spec changes, the implementation of this feature would require corresponding updates to the Go compiler (gc), go vet, and other related tools.
Reflection: The spec might need to outline how reflection interacts with the new composite types. (e.g., what information is available via reflect.TypeOf, etc.)
Informal Change
"Alright class, today we're going to talk about something Go doesn't currently do, but some of us think it should. It's about combining types, kind of like how you might mix ingredients to make a new dish.
Imagine you have two structs: one for Address and one for Person. Address has Street and City, and Person has Name and Age. Now, what if you want to represent a UserInfo that has all of this information? Right now, in Go, you'd probably do something like this:
See how we either embed the structs or manually copy the fields? That's okay, but it can get a bit tedious, especially if you have lots of fields or lots of structs you want to combine. Plus, if you change Address later, you have to remember to update UserInfo too!
So, the idea we're exploring is to make this process easier and more expressive. We're thinking about adding a way to directly combine types, almost like saying, "Hey Go, I want a new type called UserInfo that's basically an Address and a Person all rolled into one."
We have two main approaches in mind:
Again, this would give you a UserInfo type with all the fields.
Now, there are some details we'd need to figure out. What if both Address and Person have a field called Location? How do we handle that? We'd need rules for resolving these "name collisions." Also, how would methods work? If both types have a String() method, which one does UserInfo use?
This is what we're trying to figure out! We're not saying this will be added to Go, but we want to discuss it, explore the possibilities, and see if it's a good fit for the language. It's all about making Go even better!"
Is this change backward compatible?
Yes, because this feature just adds a new typing flexibility to the language.
Orthogonality: How does this change interact or overlap with existing features?
No, actually the purpose of this feature is to enable developers to set up project data structuring in a more organized way. I need it to reduce workload and complexity by combining the same fields in an easy way instead of defining them over and over again in multiple fields.
Would this change make Go easier or harder to learn, and why?
I don't think it will affect the learning speed of go very much.
Cost Description
The cost of this feature is that it is likely to force GoLang to be more inclined to include typing features like those found in most modern languages. For example, other users may request “typescript” features from you. Golang is a very powerful language, but it is a bit behind in some areas. Especially with typing, I find it very difficult to make things as flexible as I expect.
Changes to Go ToolChain
I don't know
Performance Costs
I don't know
Prototype
Scenario 1: User Profile
Imagine you have separate structs for User and Profile:
With type intersection/composition, you could create a UserProfile type that combines both:
Now, UserProfile would have all the fields: ID, Name, Email, Bio, Location, and Website.
Benefit: Avoids redundant field definitions, promotes code reuse.
Scenario 2: Data Transfer Object (DTO)
Suppose you have a database model and you want to create a DTO for your API:
You could use composition to include the relevant fields:
Benefit: Keeps DTOs concise and focused, reduces the risk of inconsistencies between models and DTOs.
Scenario 3: Event Handling
Let's say you have different event types:
You could create a generic Event interface and then use composition to define specific event types:
Benefit: Enables more structured and organized event handling, promotes type safety.
Scenario 4: Embedding for Behavior
Go's embedding is already a form of composition, but let's illustrate how methods can be implicitly "promoted":
Benefit: Allows types to inherit behavior from other types, facilitating code reuse and a more object-oriented style (although Go is not strictly an OO language).
Important Notes:
These examples use the hypothetical composes keyword for illustration. The actual syntax would need to be defined.
Name collisions (when two composed types have the same field or method name) would need to be handled.
The specific semantics of method promotion and type identity would need to be carefully considered.
The text was updated successfully, but these errors were encountered: