Workaround for No parameterized methods in go generics draft

280 views
Skip to first unread message

Marcus Manning

unread,
Jan 13, 2021, 3:39:11 PM1/13/21
to golang-nuts

Regarding the the section of No parameterized methods in go generics draft:

package p1 // S is a type with a parameterized method Identity. type S struct{} // Identity is a simple identity method that works for any type. func (S) Identity[T any](v T) T { return v } package p2 // HasIdentity is an interface that matches any type with a // parameterized Identity method. type HasIdentity interface { Identity[T any](T) T } package p3 import "p2" // CheckIdentity checks the Identity method if it exists. // Note that although this function calls a parameterized method, // this function is not itself parameterized. func CheckIdentity(v interface{}) { if vi, ok := v.(p2.HasIdentity); ok { if got := vi.Identity[int](0); got != 0 { panic(got) } } } package p4 import ( "p1" "p3" ) // CheckSIdentity passes an S value to CheckIdentity. func CheckSIdentity() { p3.CheckIden

But package p3 does not know anything about the type p1.S. There may be no other call to p1.S.Identity elsewhere in the program. We need to instantiate p1.S.Identity[int] somewhere, but how?

The natural way would be to fetch instance sets (sets containing method implementations for any specified interface, here HasIdentity) as a matter of reflection.

However, solving instance resolution over reflection alludes to the following questions:

  • Does reflection support generic instantiation?
  • Are generic types/functions are part of the runtime type information selectable for runtime reflection?
  • Are type to interface conformance relationships are part of the runtime type information?

If all these questions can be answered by yes, then the case highlighted above is solvable.

Alternatively, modulating the "CheckIdentity" function by the compiler to:

func CheckIdentity(v interface{}, hiddenHasIdentityInstanceContainingPointerToIdentityMethod) { if vi, ok := v.(p2.HasIdentity); ok { if got := vi.Identity[int](0); got != 0 { panic(got) } } }

serves providing an instance automatically by calling "CheckIdentity" function. This hidden parameter needs to be up-propagated to all callers passing p1.S down the caller-callee hierarchy.

Ian Lance Taylor

unread,
Jan 13, 2021, 5:38:21 PM1/13/21
to Marcus Manning, golang-nuts
On Wed, Jan 13, 2021 at 12:39 PM Marcus Manning <icon...@gmail.com> wrote:
>
> Does reflection support generic instantiation?

No. In the current proposal, generic functions and types are not
visible at run time. Only instantiated functions and types are
visible. So there is no way to even name a generic function/type that
should be instantiated.

> Are generic types/functions are part of the runtime type information selectable for runtime reflection?

No.

> Are type to interface conformance relationships are part of the runtime type information?

Yes, using reflect.Type.Implements.

Ian

Marcus Manning

unread,
Jan 14, 2021, 7:33:53 AM1/14/21
to Ian Lance Taylor, golang-nuts
Hmm..., then we should appreciate the alternative to generate a hidden
instance parameter for each instantiated type in the function body. The
hidden parameters might need to be up-propagated. Further, using
reflection, these hidden parameters have to be optionals or include nil
as we don't know if instances for a type parameter are available:

```

func CheckIdentity(v interface{})

{

     if vi, ok := v.(p2.HasIdentity); ok {

         if got := vi.Identity[int](0); got != 0 {

             panic(got)

        }

    }

}

 package p4 import ( "p1" "p3" )

p3.CheckIdentity(p1.s,
hiddenParameterIdentityInt?=p1.InterfaceInstanceOfSForInt)

p3.CheckIdentity(p1.someOtherStructNotHavingIdentityInstanceForInt,hiddenParameterIdentityInt?=nil)

```

roger peppe

unread,
Jan 14, 2021, 7:50:29 AM1/14/21
to Marcus Manning, golang-nuts
On Wed, 13 Jan 2021 at 20:38, Marcus Manning <icon...@gmail.com> wrote:

Regarding the the section of No parameterized methods in go generics draft:

package p1 // S is a type with a parameterized method Identity. type S struct{} // Identity is a simple identity method that works for any type. func (S) Identity[T any](v T) T { return v } package p2 // HasIdentity is an interface that matches any type with a // parameterized Identity method. type HasIdentity interface { Identity[T any](T) T } package p3 import "p2" // CheckIdentity checks the Identity method if it exists. // Note that although this function calls a parameterized method, // this function is not itself parameterized. func CheckIdentity(v interface{}) { if vi, ok := v.(p2.HasIdentity); ok { if got := vi.Identity[int](0); got != 0 { panic(got) } } } package p4 import ( "p1" "p3" ) // CheckSIdentity passes an S value to CheckIdentity. func CheckSIdentity() { p3.CheckIden

This code seems corrupted (no newlines) and incomplete. It's hard to understand what your question is about without seeing the motivational code.

Marcus Manning

unread,
Jan 14, 2021, 9:04:49 AM1/14/21
to roger peppe, golang-nuts
On 1/14/21 1:49 PM, roger peppe wrote:
This code seems corrupted (no newlines) and incomplete. It's hard to understand what your question is about without seeing the motivational code.

I hate mailing lists for exactly this reason.

Here is the code again:


```

    p3.CheckIdentity(p1.S{})
}

```


Is there any option to highlight code here?

Ian Lance Taylor

unread,
Jan 14, 2021, 2:00:12 PM1/14/21
to Marcus Manning, golang-nuts
On Thu, Jan 14, 2021 at 4:33 AM Marcus Manning <icon...@gmail.com> wrote:
>
> Hmm..., then we should appreciate the alternative to generate a hidden
> instance parameter for each instantiated type in the function body. The
> hidden parameters might need to be up-propagated. Further, using
> reflection, these hidden parameters have to be optionals or include nil
> as we don't know if instances for a type parameter are available:

The current proposal tries pretty hard to avoid requiring a specific
implementation strategy.

Ian

Ian Lance Taylor

unread,
Jan 14, 2021, 3:29:45 PM1/14/21
to Marcus Manning, golang-nuts
On Thu, Jan 14, 2021 at 12:07 PM Marcus Manning <icon...@gmail.com> wrote:
> I don't know if this is really specific as it doesn't require that much
> to a backend. Usually, this is how things work with generic
> typeclasses/interfaces/traits/concepts/, you are required to provide
> witness tables along with.
>
> But because the example uses reflection, it may be more valuable just to
> store reified conformances like triples as RTTI:
>
> (type: p1.S,witness: p1.Identity[Int],interface: p2.HasIdentity[Int])
> //in a global table?
>
> Then we ask for example: reflection give me the witness for S regarding
> HasIdentity //that would be idiomatic, I think.
>
> In think, if we could add such a functionality later without to break
> existing code we're fine to implement generics without this feature,
> otherwise we can't introduce the proposed functionality later.

What you are describing is basically how Go implements interface
types. I don't understand how to tie this back to generics. In the
current proposal, type parameters are a purely compile-time concept.
A valid implementation would be full monomorphization aka stenciling
of all type arguments, similar to the way that C++ templates work.

Ian

Marcus Manning

unread,
Jan 14, 2021, 3:43:45 PM1/14/21
to Ian Lance Taylor, golang-nuts

roger peppe

unread,
Jan 14, 2021, 6:07:21 PM1/14/21
to Marcus Manning, golang-nuts
FWIW I think that one possible approach to allowing methods (and potentially function values) with type parameters might be to allow instantiating them only with a limited set of "shapes" of data (for example, only pointer-like types). Then I think there's the possibility that the method or function could be compiled once for each allowed shape but still allow instantiation with an arbitrary number of types, without the necessity to statically enumerate all the possible types for that function.

We need to instantiate p1.S.Identity[int] somewhere, but how?

If one did something like the above, I guess you'd look up the implementation by shape (8 bytes, non pointer) from the type info in the interface value, then call the compiled function with the appropriate meta information based on that. Details left as an exercise for the reader :)

I don't see that reflection per se would need to be involved, any more than reflection is involved when doing a dynamic type conversion.

  cheers,
    rog.

On Wed, 13 Jan 2021 at 20:38, Marcus Manning <icon...@gmail.com> wrote:
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/420e8a74-d9f8-4df9-9d7b-efffb642fbe8n%40googlegroups.com.

Ian Lance Taylor

unread,
Jan 14, 2021, 8:39:55 PM1/14/21
to roger peppe, Marcus Manning, golang-nuts
On Thu, Jan 14, 2021 at 3:07 PM roger peppe <rogp...@gmail.com> wrote:
>
> FWIW I think that one possible approach to allowing methods (and potentially function values) with type parameters might be to allow instantiating them only with a limited set of "shapes" of data (for example, only pointer-like types). Then I think there's the possibility that the method or function could be compiled once for each allowed shape but still allow instantiation with an arbitrary number of types, without the necessity to statically enumerate all the possible types for that function.
>
>> We need to instantiate p1.S.Identity[int] somewhere, but how?
>
>
> If one did something like the above, I guess you'd look up the implementation by shape (8 bytes, non pointer) from the type info in the interface value, then call the compiled function with the appropriate meta information based on that. Details left as an exercise for the reader :)
>
> I don't see that reflection per se would need to be involved, any more than reflection is involved when doing a dynamic type conversion.

I don't think that approach would work for an implementation that
re-compiles the generic function for each set of type arguments.
There would be no implementation to select. And, of course, the
specific GC shape required might be missing. I think that any choice
we make here has to always work reliably.

Ian
Reply all
Reply to author
Forward
0 new messages