-
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: reflect: add method Type.Underlying() #39574
Comments
I don't think that a new Can you explain when this feature is needed? In general the reflect package tries to provide the functionality that is available in the language. As far as I can tell the language doesn't give you any way to discover the underlying type of some value. You can convert to the underlying type if you happen to know it, but you can't discover the underlying type. Am I missing something? |
The reason is clear, thanks. It can be omitted, and the other the steps would still suffice.
My answer is simple: Go specs do provide a way to use (not discover) the underlying type U of some type T - by defining another type. Example: type T /*...*/ // underlying type U not directly known/available to user code
type T2 T // underlying type of T2 will be U, thus equivalent to "type T2 U" Appropriately, the main use case of func Bind(named Type, underlying Type, methods []Method) /*...*/ or func SetUnderlying(named Type, underlying Type) is: the incomplete 'named' type is completed, by setting its underlying type to 'underlying', which must not be a defined type. This is exactly the same semantics as the method SetUnderlying() of More concretely, the var T reflect.Type = /* ... */
var T2 = reflect.NewNamed("", "T2")
T2.SetUnderlying(T.Underlying()) Clearly, one could argue that the method But type Type interface {
Field(int) StructField
NumField() int
Method(int) Method
NumMethod() int
} and analogously for Instead, such discovery functionality is fundamental at runtime - it allows to write general code that discovers the fields and methods of an arbitrary type or value, and uses them. My opinion is that the underlying type of a type is a property, analogous to the fields or methods of a type, and that |
That's a fair point about types like Is there a use for |
What are those missing features? If the missing features are only NamedOf and StructOf, we should probably be discussing whether and how to implement them inside reflect, not what APIs to provide to make them possible outside reflect. Inside reflect, we can do anything we want, without defining new API. It's unclear why either NamedOf or StructOf needs Underlying when implemented inside reflect. (And in any event, they'd have access to unexported fields and the like.) I'm just trying to understand better specific cases where Underlying would be used other than implementing more parts of reflect. |
Here's a list of the missing features of
The the API I am proposing (
Well, the semantic of
|
If we don't need |
Well, in the end it is a design choice. If
|
If that is the main reason for this proposal, then it should be folded into some other proposal, and not stand by itself. Thanks. |
Based on the discussion above, this seems like a likely decline. |
I agree it's an addition mostly useful together with proposal: reflect: allow creation of recursive types #39528. We will see if the final proposal to allow creating recursive |
I'm only loosely following the discussion above. Below is my use case that may be relevant - I can't tell. For the definitions More on my use case: I'm implementing a CSV parsing package that has a function |
The definitions type U = struct{...} // type alias
type T U; create two types: the named type Given To be precise, there is a partial workaround: one could detect that Thus your use case would significantly benefit from this proposal - without it, from a type |
Thanks @cosmos72 for the explanation. Regarding the above comment by @ianthehat:
I don't know Go's internals at all. From afar it seems like an extra word of storage for each type or less if cleverly encoded. Maybe you can elaborate on the storage cost if it is a concern.
In practice, reflect is often used when the language isn't expressive enough. We use the reflect package to see what the programmer usually sees. In a sense, the language already offers a way to discover the underlying type of T - by reading its definition. |
The need for additional storage can be removed. My implementation outline above has four steps: the first three are basically three different caches: useful, but only the third one is strictly necessary - to guarantee that The additional field is the first of such caches, and it can be safely omitted. This is important because, as @ianlancetaylor pointed out, Go tries extremely hard to minimize the space needed by
That's a much clearer explanation than mine. Thanks! |
Agreed. I tried using ConvertibleTo(), and it's not great semantically. In the case of encoding a type Distance with underlying type float64, it is reasonable to use float64's registered encoder if none is registered for Distance. However, lots of types have an underlying type float64, and it would be odd to use the encoder for, say, Angle, to decode Distance. The two are convertible to each other but are quite different from a type hierarchy perspective. This is where Underlying() is needed in the encoding use case. |
No change in consensus, so declined. |
Is there a workaround for this since the reflect API will not be expanded?
…On Wed, Jul 8, 2020 at 10:07 AM Russ Cox ***@***.***> wrote:
No change in consensus, so declined.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#39574 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAUO54KSVCKMLUUF4A5OQDR2SRTLANCNFSM4N46GNXA>
.
|
There is a partial workaround, i.e. create manually the underlying type with the existing There is no way to get the underlying type of a named interface type, package reflect_underlying
import (
r "reflect"
"unsafe"
)
var basic = []r.Type{
r.Bool: r.TypeOf(false),
r.Int: r.TypeOf(int(0)),
r.Int8: r.TypeOf(int8(0)),
r.Int16: r.TypeOf(int16(0)),
r.Int32: r.TypeOf(int32(0)),
r.Int64: r.TypeOf(int64(0)),
r.Uint: r.TypeOf(uint(0)),
r.Uint8: r.TypeOf(uint8(0)),
r.Uint16: r.TypeOf(uint16(0)),
r.Uint32: r.TypeOf(uint32(0)),
r.Uint64: r.TypeOf(uint64(0)),
r.Uintptr: r.TypeOf(uintptr(0)),
r.Float32: r.TypeOf(float32(0)),
r.Float64: r.TypeOf(float64(0)),
r.Complex64: r.TypeOf(complex64(0)),
r.Complex128: r.TypeOf(complex128(0)),
r.String: r.TypeOf(""),
r.UnsafePointer: r.TypeOf((unsafe.Pointer(nil))),
}
func Underlying(t r.Type) (ret r.Type) {
if t.Name() == "" {
// t is an unnamed type. the underlying type is t itself
return t
}
kind := t.Kind()
if ret = basic[kind]; ret != nil {
return ret
}
switch kind {
case r.Array:
ret = r.ArrayOf(t.Len(), t.Elem())
case r.Chan:
ret = r.ChanOf(t.ChanDir(), t.Elem())
case r.Map:
ret = r.MapOf(t.Key(), t.Elem())
case r.Func:
n_in := t.NumIn()
n_out := t.NumOut()
in := make([]r.Type, n_in)
out := make([]r.Type, n_out)
for i := 0; i < n_in; i++ {
in[i] = t.In(i)
}
for i := 0; i < n_out; i++ {
out[i] = t.Out(i)
}
ret = r.FuncOf(in, out, t.IsVariadic())
case r.Interface:
// not supported
case r.Ptr:
ret = r.PtrTo(t.Elem())
case r.Slice:
ret = r.SliceOf(t.Elem())
case r.Struct:
// only partially supported: embedded fields
// and unexported fields may cause panic in r.StructOf()
defer func() {
// if a panic happens, return t unmodified
if recover() != nil && ret == nil {
ret = t
}
}()
n := t.NumField()
fields := make([]r.StructField, n)
for i := 0; i < n; i++ {
fields[i] = t.Field(i)
}
ret = r.StructOf(fields)
}
return ret
} |
@gonzojive It's very unlikely that we would ever support a mechanism for finding |
Thanks @ianlancetaylor. Is there a philosophy behind not intending to support such a mechanism? My supposition is that Go users should not make much of the chain of named types between a type and its underlying type. For example, |
In Go there are two main reasons to use a defined type: to prevent accidentally combining values of two different types, and to define methods on a type. When you write |
Introduction
The
reflect
package provides at runtime most - but not all - the possible operations on Go types.As such, it is extremely useful for advanced Go programs that manipulate types, allowing in many cases to forgo code generation and/or ugly workarounds that use
interface{}
instead of concrete types. It's also immensely useful for existing Go interpreters, which use it heavily.Some of the missing features are currently very difficult to implement. Some examples:
StructOf
: reflect: StructOf doesn't generate wrapper methods for embedded fields #15924 reflect: StructOf should support embedding types with non-exported methods #20015 reflect: StructOf forbids anonymous fields without name #24781 reflect: permit unexported fields in StructOf #25401 reflect: can't call methods on StructOf with embedded interface field #38783Other features, while relatively easier to implement, are still complicated - for example creating new named and/or recursive types without methods: @TheCount proposal: reflect: allow creation of recursive types #39528
One of the missing features is "getting the underlying Type of a Type":
it's a relatively smaller feature and, if I had to guess, comparably easier
to implement. Yet it would be very useful for advanced usages: especially, but not only, for Go interpreters.
To give a practical example, my interpreter gomacro contains a whole package
xreflect
(~7000 lines of code) to emulate the missing features ofreflect
.Some hundreds of them are dedicated to work around the absence of
reflect.Type.Underlying
Proposal
The proposal itself is very simple:
this issue proposes an additional method of
reflect.Type
:About the implementation:
it could be implemented similarly to the function
reflect.PrtTo
:underlying typeOff
inside the structreflect.rtype
and, if non-zero, return itunderlyingMap
rtype
and removing the name and (for non-interfaces) also the methodsLimitation: an underlying struct type may have methods due to embedded fields, but they could be ignored - at least in a first version.
The text was updated successfully, but these errors were encountered: