Signature Switch - third path to Go generics

145 views
Skip to first unread message

Wojciech S. Czarnecki

unread,
Dec 31, 2020, 1:37:27 PM12/31/20
to golang-nuts
I regard current Team's proposal a way better than the first iteration. But -
while I see a need for generic way to write common code, I also share
concerns about readability and future abuse of the materialized Go generics
being similar to "the other languages". I consciously did not patricipate in the
[Generics, please go away!](https://groups.google.com/g/golang-nuts/c/LEEuJPOg0oo)
bikeshed, but I would like to share an idea that may possibly reconcile both camps.

"Signature switch" - readable generic code preserving the Go way.

Generic function declaration is https://golang.org/ref/spec#Function_declarations
with at least one type in the Signature given as the ascii character "+" followed
by single capital ascii letter, further reffered to as "TypeLetter"; and with a single,
non empty, signature switch present in the function body:

func f (x +T) (r +R) { // generic function
switch func.(type) { // Signature Switch here. Resolves at COMPILE TIME.
case func(x int) (r int): // call-site signature to match
r = x/2 // instantiated code, T and R are now ints
}
return r
}

More to read at: https://play.golang.org/p/Q1ry4KPoOOJ

Hope this helps,

--
Wojciech S. Czarnecki
<< ^oo^ >> OHIR-RIPE

K. Alex Mills

unread,
Dec 31, 2020, 2:15:59 PM12/31/20
to Wojciech S. Czarnecki, golang-nuts
At a glance, this feels to me like it is more complicated than the current generics proposal. That said, it also seems very original so I have to give credit there.

This proposal seems to put the responsibility for monomorphizing generic code onto the programmer. I'm not sure why it would be simpler than monomorphizing generic functions directly, which we can do today. Although I guess under this proposal all the monomorphized versions would be able to share similar function signatures and we can't do that today.

I do worry that in case you have two generic parameters, this proposal would seem to require NxM cases to be explicitly written out. That feels like a lot of work as compared to the current proposal, in which the compiler can inspect the callsites and automatically generate only the binary code needed to satisfy the types in use.


--
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/20201231193635.0d27177a%40xmint.

Ian Lance Taylor

unread,
Dec 31, 2020, 2:39:35 PM12/31/20
to Wojciech S. Czarnecki, golang-nuts
Thanks, it's an interesting idea.

That said, one of the minimal requirements that I think applies to any
generics proposal is the ability to write a compile-time-type-safe
container, such as a concurrent hash map. It should be possible to
declare a hash map that takes any comparable type as a key type, and
any type as a value type, and provide methods to insert and look up
values in the hash map. It should be possible to use that hash map
safely without writing any type assertions that might fail at run
time. I don't see how to do that with this suggestion.

Ian

Wojciech S. Czarnecki

unread,
Jan 1, 2021, 12:30:18 AM1/1/21
to golang-nuts, K. Alex Mills
Dnia 2020-12-31, o godz. 13:15:00
"K. Alex Mills" <k.alex...@gmail.com> napisał(a):

> At a glance, this feels to me like it is more complicated than the current
> generics proposal. That said, it also seems very original so I have to give
> credit there.

It is just the other kind, as I see it. You know, it was born just yesterday :)
I forbade matching on the result parameter type, but I think it might
be part to match, too. See example at the end.

> This proposal seems to put the responsibility for monomorphizing generic
> code onto the programmer. I'm not sure why it would be simpler than
> monomorphizing generic functions directly, which we can do today.

It is the author of the generic function that bears the burden - yes.
But thousands others using her older code need not even know that some day
it went generic and "legacy" signature went to the one of cases. Ie. I as an author
may start with plain Go function, then make it generic just when I need same
functionality for other but similar set of parameter types. See Min(x, y) example in
the playground document.

> I guess under this proposal all the monomorphized versions would be able to
> share similar function signatures and we can't do that today.

All call sites to the identifier share the signature shape, ie number of parameters, yes.

> I do worry that in case you have two generic parameters, this proposal
> would seem to require NxM cases to be explicitly written out.

No. Not that much explicitly. See lines 23..32 in the playground document.

Eg. the "underlying" type syntax lets us write cases that encompass way many of NxM
combinations in a few branches of the Signature switch, while analysis (read time)
stays linear: find declaration, find case that matches your parameters, read.
Also, if given set of parameter types matches a case signature, compiler will
instantiate (or even reuse in the far future) respective case's code. Otherwise
compile error will tell us that our current types here are not served (yet).
Or that we eg. need to give a type to the untyped constant we used.

Below float to int conversion example deals with any call site that call us with any int type
that happens to be based on int32, int64 as "int" and float32, float64 "float". Func ToInt
returns result of the x parameter type and an ok flag set to true if conversion went ok
If there was an overflow, func ToInt returns respective MaxInt and false.

Note that (for now) there is no match on the result parameter type, so the ToInt got
a parameter that might not be needed were result parameters matched too.
Such version is below, too.

// https://play.golang.org/p/T97kLYaHS9e
// negative numbers branch left out for brevity
func ToInt (x +T, y +U) (r +T, ok bool) {
switch func.(type) {
case func(x (int32), y (float64)) (r T, ok bool): fallthrough
case func(x (int32), y (float32)) (r T, ok bool): // (type) underlying type of
lim := float64(math.Nextafter32(float32(math.MaxInt32+1), 0))
if float64(y) > lim { // Mmm, Is it ok? I should study IEEE754 more
return T(math.MaxInt32), false
}
return T(int32(y)), true

case func(x (int64), y (float32)) (r T, ok bool): fallthrough
case func(x (int64), y (float64)) (r T, ok bool):
lim := math.Nextafter(float64(math.MaxInt64+1), 0)
if float64(y) > lim {
return T(math.MaxInt64), false
}
return T(int64(y)), true
}
return // not reached
}

// if result parameters can be matched
func ToInt (f +F) (r +T, ok bool) {
switch func.(type) {
case func(f (float64)) (r (int32), ok bool): fallthrough
case func(f (float32)) (r (int32), ok bool):
lim := float64(math.Nextafter32(float32(math.MaxInt32+1), 0))
if float64(f) > lim {
return T(math.MaxInt32), false
}
return T(int32(f)), true

case func(f (float64)) (r (int64), ok bool): fallthrough
case func(f (float32)) (r (int64), ok bool):
lim := float64(math.Nextafter(float64(math.MaxInt64+1), 0))
if float64(f) > lim { // Mmm, Is it ok? I should study IEEE754 more
return T(math.MaxInt64), false
}
return T(int64(f)), true
}
return // not reached
}


> That feels like a lot of work as compared to the current proposal, in which the
> compiler can inspect the callsites and automatically generate only the
> binary code needed to satisfy the types in use.

This is true also for the Signature switch - it is compiler that picks pieces of code
according to the rules (case signatures) set by the programmer. It certainly
might be more work for the author of the generic code but for the readers
the Go's clarity is preserved, IMO.

Not to mention that more work means also less abuse ;).

Nonetheless, it is just an ad-hoc idea stem from the partisan fights I read past year.
One to be looked at in leisure time and exercise on the paper, if at all :)

Happy New Year!

wilk

unread,
Jan 1, 2021, 6:34:33 AM1/1/21
to golan...@googlegroups.com
On 31-12-2020, Ian Lance Taylor wrote:
> On Thu, Dec 31, 2020 at 10:37 AM Wojciech S. Czarnecki <oh...@fairbe.org> wrote:
>>
>> I regard current Team's proposal a way better than the first iteration. But -
>> while I see a need for generic way to write common code, I also share
>> concerns about readability and future abuse of the materialized Go generics
>> being similar to "the other languages". I consciously did not patricipate in the
>> [Generics, please go away!](https://groups.google.com/g/golang-nuts/c/LEEuJPOg0oo)
>> bikeshed, but I would like to share an idea that may possibly reconcile both camps.
>>
>> "Signature switch" - readable generic code preserving the Go way.
>>
>> Generic function declaration is https://golang.org/ref/spec#Function_declarations
>> with at least one type in the Signature given as the ascii character "+" followed
>> by single capital ascii letter, further reffered to as "TypeLetter"; and with a single,
>> non empty, signature switch present in the function body:
>>
>> func f (x +T) (r +R) { // generic function
>> switch func.(type) { // Signature Switch here. Resolves at COMPILE TIME.
>> case func(x int) (r int): // call-site signature to match
>> r = x/2 // instantiated code, T and R are now ints
>> }
>> return r
>> }
>>
>> More to read at: https://play.golang.org/p/Q1ry4KPoOOJ
>>
>> Hope this helps,
>
> Thanks, it's an interesting idea.
>
> That said, one of the minimal requirements that I think applies to any
> generics proposal is the ability to write a compile-time-type-safe
> container, such as a concurrent hash map.

I understand in his description that it's type safe at compile time.

>> switch func.(type) { // Signature Switch here. Resolves at COMPILE TIME.
^^^^^^^^^^^^^^^^^^^^^^^^^

I like it this way, it's look like closure and immediatly understandable
for go users (maybe func f( x[T]) (r [R]) ?)

--
wilk

Axel Wagner

unread,
Jan 1, 2021, 6:55:56 AM1/1/21
to wilk, golang-nuts
On Fri, Jan 1, 2021 at 12:34 PM wilk <wi...@flibuste.net> wrote:
On 31-12-2020, Ian Lance Taylor wrote:
> That said, one of the minimal requirements that I think applies to any
> generics proposal is the ability to write a compile-time-type-safe
> container, such as a concurrent hash map. 

I understand in his description that it's type safe at compile time.

But not a container.
Reply all
Reply to author
Forward
0 new messages