-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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: allow interface types to match fields as well as methods #23796
Comments
For getters, it would suffice to allow explicit conversion to a struct type with fewer fields, like
|
For the record: This idea has been discussed in the very early days of Go (the first months). I am not a fan as it destroys the notion of an interface being an abstract type which doesn't imply a specific implementation. This could be finessed by defining that an "interface field" is simply syntactic sugar for setter and getter methods. But at that point, the problem is not about interfaces anymore, but a convenient way to write setters and getters. I'd rather address that problem directly. |
@jimmyfrasche Sounds like |
I’d try to use this for additional type expressiveness by composition via embedding. Without a real-world example I’m having a tough time seeing the tradeoffs. The thread mentions http.ResponseWriter’s Header method which seems like a clear win in code simplicity with this. |
Modelling an interface field as a getter and setter undermines the notion of interfaces describing abstract behaviour. Getter and setters are not behaviours, they are state. |
@pciet Lack of a real-world example is a fair complaint. By the nature of the proposal, real-word examples are somewhat complex. They arise, for example, when using many different protocol buffer types in proto3. Often several different protocol buffer types will have the same field name with the same type, because they represent the same concept. However, there is no struct embedding going on, because the Go code used for protocol buffers is machine generated by the protocol buffer compiler. So now let's suppose you want to write a function that operates on several different protocol buffer types in the same way by accessing the field that exists in all of them. In Go that is naturally represented using an interface, but to do it here requires writing a new type for each protobuf type, that embeds that protobuf type, and defines getter/setter methods, where the getter/setter methods all look exactly the same. If we could could declare an interface type with a field, all of that boilerplate could be avoided. So that is still pretty abstract but I hope it gives a flavor for the kind of case where this could be useful. Today people handle this kind of thing using the reflect package and |
For the protocol buffer example maybe the generator should have an option to also generate getter/setter methods (or a separate program that bulk generates getter/setters for a struct so that it could be used with). I've had similar issues but the only example I can think of is with the Name field on various types in go/ast. Either manually adding appropriate getters or a top-level func like In both cases, though, the problem involves a closed set of definitions so it seems like an API design issue rather than a language issue. Are there any ad hoc examples when this is needed for structs from vastly different sources (packages, types generated by different means, etc.)? |
Not that I know of. |
Just a thought: rather than changing the definition of an interface to include struct fields, structs could be special in that struct fields can satisfy a zero argument interface method with the same name and type. For example: type I interface {
F() int
}
type S struct {
E, F, G int
}
func main() {
var i I = S{}
i.F() // returns a copy of the field S.F
} I haven't thought through all of the implications of this change. |
Also |
Looks like a convoluted way of implementing properties without calling them properties. |
We wouldn't need to compile to a function call. The itab could contain offsets in the underlying struct of all the fields declared in the interface.
This would also handle I'm pretty on the fence about this proposal. |
I'd like to point out that typescript supports this, and from what I've seen its used to allow passing literal objects to functions that accept such an interface. I'm not particularly for the original proposal, however I think @bontibon has an interesting take on the idea, and allows any type to implement the interface, while making the implementation easier for structs. I've personally had types in the past that needed to implement the following interface: type Err interface {
Err() error
} and the associated structs would look a bit weird, like: type Foo struct {
err error
}
func (f Foo) Err() error {
return f.err
} |
This proposal would lead to interfaces forcing structs as types to implement them, whereas today it's possible to have the implementation be a completely different type, like a func or a string, for example, see https://play.golang.org/p/irTsnlldaea as an example for this. package main
import "fmt"
type (
demo func()
hello string
Helloer interface {
Hello()
}
)
func (demo) Hello() {
fmt.Println("Hello, playground")
}
func (h hello) Hello() {
fmt.Println("Hello, " + h)
}
func call(h Helloer) {
h.Hello()
}
func main() {
d := demo(func() {})
call(d)
h := hello("world")
call(h)
} If the problem is accepting a restricted, common subset of fields, maybe a different venue to pursue this would be to allow passing of structs that get narrowed down to a subset of the fields. For example, maybe this would be a way to solve passing data around: https://play.golang.org/p/57BBaoUlMhu package main
import "fmt"
type (
BaseStruct struct {
F2 string
}
Struct1 struct {
F1, F2 string
}
Struct2 struct {
F2, F3 string
}
)
func call(b BaseStruct) {
fmt.Printf("f2: %v\n", b.F2)
}
func main() {
s1 := Struct1{
F1: "f1",
F2: "f2",
}
call(s1)
s2 := Struct2{
F2: "f2",
F3: "f3",
}
call(s2)
} Obviously, this does not solve the problem of calling methods on the type itself. I can't think of any better answer than: if data is needed, accept a common struct, if functionality is needed, accept an interface. One could say: why not use embedding of BaseStruct in the Struct1 and Struct2. Obviously, embedding would be preferable but in this case please assume that Struct1 and Struct2 are coming from two different place which the author does not full control over. Reading / writing into the fields of BaseStruct should be passthrough to the value that was used to call the function. |
That is already possible today: if a particular type is inspected using reflection, it may be required to be a pointer-to-struct or a channel or obey any number of other invariants. For example, To enforce the level of behavioral abstraction you'd like today, you'd have to strike reflection from the language. Given that that's not at all likely to happen (much as I might wish for it), I don't see that it's all that harmful to codify a particularly common type constraint in the interface definition. That is: we already have those sorts of constraints, and to me it seems strictly positive to promote at the least the common ones from ad-hoc comments into the language proper. |
We haven't seen any strong arguments in favor of this proposal. Retracting it. |
Just in case this gets reopened: Do we consider fields of a struct as part of their behavior or not? If yes, then I think interfaces should be able to capture that. This comes up often when I have multiple types that I pass to a function. They are supposed to have a particular field, but I cannot express that through the interface that the function accepts. The two workarounds are:
I think allowing to express requirement for existence of fields in interfaces would have resolved this issue cleanly. |
My apologies. There are times when I wish to define implementation. On those rare occasions I use Setters and Getters in my interface. I am not a purist, I do what is convenient and readable. I do not like exposing properties without a setter if I want to validate the data. And setters and getters in the interface means that the underlying implementation can be completely different for unit testing. |
But to my point, your are describing properties of the implementation; “it has the field X, you can get it and set it thusly”. That is not behaviour, David holds a pen, I got the pen and it was blue, is not behaviour, it is state.
… On 5 Jun 2018, at 08:42, David Skinner ***@***.***> wrote:
Dave Cheney Modelling an interface field as a getter and setter undermines the notion of interfaces describing abstract behaviour. Getter and setters are not behaviours, they are state.
My apologies. There are times when I wish to define implementation. On those rare occasions I use Setters and Getters in my interface. I am not a purist, I do what is convenient and readable. I do not like exposing properties without a setter if I want to validate the data. And setters and getters in the interface means that the underlying implementation can be completely different for unit testing.
I am philosophically opposed to this suggestion but if adopted, it will be something I need not use.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.
|
*Dave Cheney* But to my point, your are describing properties of the
implementation; “it has the field X, you can get it and set it thusly”.
That is not behaviour, David holds a pen, I got the pen and it was blue, is
not behaviour, it is state.
I really do not want to get into an argument with Dave Cheney. I hate
losing arguments.
Considering that I sometimes influence computer science students with my
somewhat antiquated ideas,
I think I should express myself more precisely, as I would to a student. I
am open to corrections..
- We can have a transparent data type by declaring a structure with the
names of the properties with an upper case first letter.
- We can have an opaque data type by declaring a structure with the
names of the properties with a lower case first letter. This enforces
information hiding, since its values can only be manipulated by calling
functions that have access to the missing information. The concrete
representation of the type is hidden from its users, and the visible
implementation is incomplete.
- We can have an opaque pointer to an undefined data type that has a
specific interface for exchange of information.
- Information hiding reduces software development risk by shifting the
code's dependency on an uncertain implementation onto a well-defined
interface. Clients of the interface perform operations purely through it so
if the implementation changes, the clients do not have to change.
With regard to the specific example above
- I define an interface for pen with the functions all pens have.
- I then define 5 types of pens
1. ChromePens cannot change colors.
2. BicPens can be any of 4 select colors.
3. SharpiePens can be any color.
4. ThermalPen is Red when hot and Blue when cold, and in the underlying
hidden data structure there is no color state, only a temperature state.
When you use the getter function Color() it is not returning a state but it
is returning a behavior.
5. DebugPen does nothing but create log entries to debug my main program
before all the other pens are defined.
With regard to the topic under discussion.
- Like C++, Go allows a hybrid type which exposes some properties and
meets the interface requirements for others.
- An interface which defines a required property, is not an interface,
it is making implementation decisions.
- Defining a setter and getter in the interface may suggest an
implementation, but it does not require it. It allows the programmer to
maintain data validity, data integrity, collateral effects, with the data
structure of his choice.
Coming up with useful abstract data types by properly defining interfaces
is an important skill in creating software. It is one with which I have
difficulty.
…On Tue, Jun 5, 2018 at 6:07 AM Dave Cheney ***@***.***> wrote:
But to my point, your are describing properties of the implementation; “it
has the field X, you can get it and set it thusly”. That is not behaviour,
David holds a pen, I got the pen and it was blue, is not behaviour, it is
state.
> On 5 Jun 2018, at 08:42, David Skinner ***@***.***> wrote:
>
> Dave Cheney Modelling an interface field as a getter and setter
undermines the notion of interfaces describing abstract behaviour. Getter
and setters are not behaviours, they are state.
>
> My apologies. There are times when I wish to define implementation. On
those rare occasions I use Setters and Getters in my interface. I am not a
purist, I do what is convenient and readable. I do not like exposing
properties without a setter if I want to validate the data. And setters and
getters in the interface means that the underlying implementation can be
completely different for unit testing.
> I am philosophically opposed to this suggestion but if adopted, it will
be something I need not use.
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub, or mute the thread.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#23796 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAGBWOWjZ8222KIy5A8q13QgvgOX0u4-ks5t5maIgaJpZM4SCkBH>
.
|
I'm opening this issue as a place to record this idea, which has come up several times. I'm not personally in favor of this proposal. An example of an earlier discussion: https://groups.google.com/d/msg/golang-nuts/ZJ5DEv_36S8/opZ__-l6XxAJ .
The proposal: allow interface types to list fields, with types, as well as methods. An interface type that lists a field may only be implemented by a struct type that has a field with the same name and type.
For example:
Note that since the interface type could be implemented by struct types that put the field at different offsets, the seemingly simple expressions/statements like
i.F
andi.F = v
would most likely be implemented by function calls.The advantage of this proposal is that when several different struct types have fields with the same names and types, and interface could be used to access those fields directly, rather than requiring each type to define boilerplate getter/setter methods.
A disadvantage is that it changes the meaning of an interface type from one that purely describes behavior to one that also describes implementation. This is particularly clear in the fact that an interface that defines a field can only be implemented by a struct type.
The text was updated successfully, but these errors were encountered: