-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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: interface adapter types #26757
Comments
I'm unclear on how you would use |
Oh yeah, I did leave that part out. Given a value |
This does seem to address the problem of wrapping known methods. How would type assertions work on a value instantiated from an adapter type? Can it uncover methods that are implemented by the underlying concrete type? |
If some form of generics were added to Go2, wouldn't they likely solve this issue? Or rather, perhaps this problem should be kept in mind for a future generics design or proposal. |
I admit I have not thought too much about it but I think that, if interface literal types make it into Go2 (issue #25860), this may be done without changing the language. If we had interface literals, I would expect to be able to create interface values with some reflect function that takes a list of functions. We can already get the method set for a type, again using the reflect package. An hypothetical reflect.Adapter function would take a value of some type and an interface type, would create the wrappers, and would call the corresponding function to generate the interface value. I am not totally sure this would be possible. It also has the drawback that you lose the information about the underlying type, although maybe an additional method may be used for that. |
@yiyus That seems not connected to having interface literals though. It just requires being able to add methods or interface values in reflect and there is already an issue about that: #16522 IMO, I would much rather have real co-/contravariance for |
@mvdan I don't see how to do this using generics, unless by generics you mean some kind of generalized compile-time code-generation mechanism. The problem is that you need to construct an adapter type |
@ianlancetaylor you're right - I hadn't thought about this well enough. It still seems to me like this is worth considering together with generics for Go2, though. Particularly since we don't yet know how generics would fit into the language. |
A related problem† is a type that would satisfy an interface if one or more of its operations were named differently. † exacerbated by introducing generics that can use operators without either operator overloading or type classes (not that I think either of those are a good match for Go) Say there is a generic
A value of
Likewise, an
(With the reverse direction, Of course, an explicit wrapper type would provide the same function without a great deal of effort. |
You can solve it on the caller side using the contracts in the current draft, but that doesn't help with the common interface per se: it moves the abstraction from run-time to compile-time. interface StringValued {
Val() string
}
contract StringValueFetcher(x T) {
var _ StringValued = x.Fetch()
}
func FetchValue(type Fetcher StringValueFetcher)(x Fetcher) string {
return x.Fetch().Val()
} |
That seems very similar to a more general problem: defining wrappers that pass through additional methods (examples: #16474, #21904, #27617; experience report here). Since this problem and that one seem closely related, it would be nice if the solution could generalize to both. (It is not obvious to me whether this proposal does.) |
This is an interesting problem space but this mechanism is too complicated. We aren't going to accept this proposal. |
In large Go programs written by many different people, useful interfaces may develop after considerable code has been written. These interfaces can capture useful common features of types, but the methods of those types may not have the same signatures. For a simplified example, we may have code like
With this kind of example we can, after the fact, define an interface that can be used for both
T1
andU1
(interface { Val() string }
). But there is no interface we can write for bothT2
andU2
, because theFetch
methods return different types. The methods are sufficiently similar that we can do it with some boilerplate code:Now
T2Wrapper
andU2Wrapper
implementI2
, thanks to the implicit conversion of the respective result parameter toI1
. But you do have to write theT2Wrapper
andU2Wrapper
types.A similar case arises when a package wants to represent errors using a specific type that is more structured than the predefined
error
type. It's natural to write methods that return that structured error type, but then the types implementing those methods do not satisfy standard interfaces such asio.Reader
. If the structured error type is itself an interface type, then boilerplate wrappers like the above would permit the interface to be satisfied (the error type must be an interface type to avoid the confusion in which anil
non-interface type becomes a non-nil
error
value).To make these sorts of cases simpler to write in Go with less boilerplate, I propose a new kind of type: an adapter type. An adapter type links a non-interface type to an interface type by returning a new type that defines the methods expected by the interface by calling the methods defined by the non-interface type.
In general the type
adapter[T]I
is a type that acts exactly likestruct { T }
, except that it has the same methods asI
where each method is implemented by calling the method of the same name onT
. Every argument type of a method ofI
must be convertible to the type of the corresponding argument of the corresponding method ofT
. Every result type of that method must be convertible to the corresponding result of the method ofI
. In general, given a methodM(A1, A2) (R1, R2)
on I and a methodM(B1, B2) (S1, S2)
on T,adapter[T]I
has a methodIf
T
does not have a methodM
, or if the number of argument or result types differ, or if the explicit conversions to the corresponding types are not valid, then the adapter type is erroneous and compilation fails.I'm not persuaded that this is a good idea, but I wanted to put it out there as a way to address an issue that arises in large Go programs. I note that this could be implemented entirely using code generation, and that might be the most appropriate choice.
The text was updated successfully, but these errors were encountered: