Re: [go-nuts] [generics] Issues with identifying the matched predeclared type

2020-07-08 Thread Steven Blenkinsop
On Wed, Jul 8, 2020 at 3:59 AM roger peppe  wrote:

>
> That's another interesting syntax. I'm not sure whether there's any
> particular advantage in mentioning the type parameter in each case,
> although I guess it does mean that the syntax for multiple matches is
> straightforward and can allow an "any" match.


The meaning of case io.Stringer: in existing type switches is asymmetrical.
Matching is based on interface satisfaction (for interface constraints),
but the matched value has an exact type of io.Stringer inside the body of
the case. Choosing either exact matching semantics *or* constraint
refinement semantics for generic type switches will be inconsistent with
one of these two behaviours, potentially leading to confusion about what a
generic type switch is doing.

The advantage of mentioning the type parameter in each case is that it
makes the parallel to type parameter lists very obvious. If matching is
done on constraint satisfaction, then this syntax should suggest the
correct semantics. Also, just the fact that it's different from the
existing type switch case clause syntax helps avoid confusion between the
two.

Another advantage of mentioning the type parameter in each case is that
it allows you to change between T and *T constraints within the same
switch, as needed. Using constraints on *T could allow you to do exact type
matching as well:

type switch {
case *T interface{ type *string }:
// `T` is treated as identical to `string` in this case
case T io.Stringer:
// `T` can be any type that satisfies `io.Stringer` in this case
}


Maybe the type matching could be written using a shorthand like the one you
were suggesting, but with the opposite meaning:

type switch {
case *T type(*string):
// `T` is treated as identical to `string` in this case
}


(Or just case T = string: could work and would be more obvious, I suppose).

I'd hope that most people using type switches for specialisation would
> include a generic fallback for unknown types. The main use cases I see for
> it are optimisation and preserving backward compatibility.


Yeah, I was more thinking that if you *functionally required* the ability
to match to an exact type in order to implement your generic function, then
it wouldn't be total over its type parameter domain. For optimizations, I
can see how you might be relying on existing code that is only written to
support particular types for one reason or another.

On Wed, Jul 8, 2020 at 3:59 AM roger peppe  wrote:

>
>
> On Wed, 8 Jul 2020 at 00:33, Steven Blenkinsop 
> wrote:
>
>> On Tue, Jul 7, 2020 at 10:44 AM, roger peppe  wrote:
>>
>>>
>>> In my description, there's no assumption that doing it over the type
>>> parameter implies a refinement of the previously set type constraints. In
>>> fact it definitely implies otherwise because (for example) if you know that
>>> a generic type parameter has the known type "string", then it's a loosening
>>> rather than a tightening of the constraint - you can now do more things
>>> with values of that type than you could previously.
>>>
>>
>> I think this is speaking at cross purposes. The identity of the type is
>> more constrained (it must be string  as opposed to any other type
>> satisfying a particular interface), but you can do more with it in your
>> generic code. You're refining the constraint on the identity of the type
>> while at the same time expanding its capabilities. An unconstrained type
>> parameter can be any type but supports the fewest operations.
>>
>
> Fair point.
>
>
>>
>> I don't think it's sufficient. Consider this program:
>>> https://go2goplay.golang.org/p/wO0JHIuHH2l. If "string" matches both
>>> "myString" and "string" itself, then the type checker would allow the
>>> assignment of a function of type "func(string) uint64" to a function of
>>> type "func(myString) uint64" with no explicit type conversion, which breaks
>>> an important safety constraint in the language. Worse, if we let an
>>> interface type match any concrete type that happens to implement that
>>> interface, then there's a serious issue because those types probably don't
>>> have the same shape, so can't be used interchangeably. For example, this
>>> program would be valid, but isn't possible to compile because io.Stringer
>>> and string have different representations:
>>> https://go2goplay.golang.org/p/_uLjQxb-z-b
>>>
>>
>> I would think that in the second case, the assignment to xx wouldn't
>> type check because T is only known to implement io.Stringer rather than
>> being identical to io.Stringer.
>>
>
> That depends on the semantics of the type switch. In my suggestion, it
> would type check because the case would only be chosen if the argument type
> is *exactly* that type (but the xx slice wouldn't be assigned to). You'd
> need to add a "type" qualifier to match on any type that implements
> io.Stringer.
>
> Perhaps this would be more clear if it was written as
>>
>> type switch {
>> case T io.Stringer:
>>   // Code 

Re: [go-nuts] [generics] Issues with identifying the matched predeclared type

2020-07-08 Thread roger peppe
On Wed, 8 Jul 2020 at 00:33, Steven Blenkinsop  wrote:

> On Tue, Jul 7, 2020 at 10:44 AM, roger peppe  wrote:
>
>>
>> In my description, there's no assumption that doing it over the type
>> parameter implies a refinement of the previously set type constraints. In
>> fact it definitely implies otherwise because (for example) if you know that
>> a generic type parameter has the known type "string", then it's a loosening
>> rather than a tightening of the constraint - you can now do more things
>> with values of that type than you could previously.
>>
>
> I think this is speaking at cross purposes. The identity of the type is
> more constrained (it must be string  as opposed to any other type
> satisfying a particular interface), but you can do more with it in your
> generic code. You're refining the constraint on the identity of the type
> while at the same time expanding its capabilities. An unconstrained type
> parameter can be any type but supports the fewest operations.
>

Fair point.


>
> I don't think it's sufficient. Consider this program:
>> https://go2goplay.golang.org/p/wO0JHIuHH2l. If "string" matches both
>> "myString" and "string" itself, then the type checker would allow the
>> assignment of a function of type "func(string) uint64" to a function of
>> type "func(myString) uint64" with no explicit type conversion, which breaks
>> an important safety constraint in the language. Worse, if we let an
>> interface type match any concrete type that happens to implement that
>> interface, then there's a serious issue because those types probably don't
>> have the same shape, so can't be used interchangeably. For example, this
>> program would be valid, but isn't possible to compile because io.Stringer
>> and string have different representations:
>> https://go2goplay.golang.org/p/_uLjQxb-z-b
>>
>
> I would think that in the second case, the assignment to xx wouldn't type
> check because T is only known to implement io.Stringer rather than being
> identical to io.Stringer.
>

That depends on the semantics of the type switch. In my suggestion, it
would type check because the case would only be chosen if the argument type
is *exactly* that type (but the xx slice wouldn't be assigned to). You'd
need to add a "type" qualifier to match on any type that implements
io.Stringer.

Perhaps this would be more clear if it was written as
>
> type switch {
> case T io.Stringer:
>   // Code here can treat the type parameter T
>   // as though the generic type list defining
>   // it was `(type T io.Stringer)`
> }
>

That's another interesting syntax. I'm not sure whether there's any
particular advantage in mentioning the type parameter in each case,
although I guess it does mean that the syntax for multiple matches is
straightforward and can allow an "any" match.

type switch {
case T1 string, T2 string:
case T1 []byte, T2 string:
case T1 string:
}

If my syntax were to support multiple type parameters, that might look like

   switch T1, T2 {
   case (string, string):
   case ([]byte, string):
   case (string, type interface{}):
   }

One could define _ to be a synonym for "type interface{}", I guess.


Of course, this wouldn't allow you to write either of your examples. But
> then, your stringHash example could be written using:
>
> func stringHash(type S interface{type string})(s S) uint64 {
> return 1
> }
>

That doesn't seem ideal to me. There are many potentially useful
non-generic functions out there and I think it would be nice to be able to
use them in this way.

>
> https://go2goplay.golang.org/p/FAaqnFalYCL
>
> I think the key tension with allowing you to match on the specific type
> passed in is that, if you actually need to do this, then that means your
> function can't handle certain type parameters which are permitted by its
> signature, i.e. it's not total over its type parameter domain.
>

ISTM that technically the function is still total over the type parameter
domain (assuming you don't panic in the default case) - it just has
different behaviour depending on the type parameters, which is exactly what
the type switch is for. Also, you can *already* do this in a slightly more
limited way, by converting to interface{} and type switching on that,
although that's restrictive because it doesn't allow you to assign back to
generic values - the types aren't unified - which is why we're talking
about this feature here.


> If interfaces allowed you to restrict the type parameters passed in to the
> specific ones you can handle, then you would necessarily also be able to
> handle each specific case by matching on interface satisfaction as well.
>

I'd hope that most people using type switches for specialisation would
include a generic fallback for unknown types. The main use cases I see for
it are optimisation and preserving backward compatibility.

  cheers,
rog.

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To 

Re: [go-nuts] [generics] Issues with identifying the matched predeclared type

2020-07-07 Thread Steven Blenkinsop
On Tue, Jul 7, 2020 at 10:44 AM, roger peppe  wrote:

>
> In my description, there's no assumption that doing it over the type
> parameter implies a refinement of the previously set type constraints. In
> fact it definitely implies otherwise because (for example) if you know that
> a generic type parameter has the known type "string", then it's a loosening
> rather than a tightening of the constraint - you can now do more things
> with values of that type than you could previously.
>

I think this is speaking at cross purposes. The identity of the type is
more constrained (it must be string  as opposed to any other type
satisfying a particular interface), but you can do more with it in your
generic code. You're refining the constraint on the identity of the type
while at the same time expanding its capabilities. An unconstrained type
parameter can be any type but supports the fewest operations.

I don't think it's sufficient. Consider this program:
> https://go2goplay.golang.org/p/wO0JHIuHH2l. If "string" matches both
> "myString" and "string" itself, then the type checker would allow the
> assignment of a function of type "func(string) uint64" to a function of
> type "func(myString) uint64" with no explicit type conversion, which breaks
> an important safety constraint in the language. Worse, if we let an
> interface type match any concrete type that happens to implement that
> interface, then there's a serious issue because those types probably don't
> have the same shape, so can't be used interchangeably. For example, this
> program would be valid, but isn't possible to compile because io.Stringer
> and string have different representations:
> https://go2goplay.golang.org/p/_uLjQxb-z-b
>

I would think that in the second case, the assignment to xx wouldn't type
check because T is only known to implement io.Stringer rather than being
identical to io.Stringer. Perhaps this would be more clear if it was
written as

type switch {
case T io.Stringer:
  // Code here can treat the type parameter T
  // as though the generic type list defining
  // it was `(type T io.Stringer)`
}


Of course, this wouldn't allow you to write either of your examples. But
then, your stringHash example could be written using:

func stringHash(type S interface{type string})(s S) uint64 {
return 1
}

https://go2goplay.golang.org/p/FAaqnFalYCL

I think the key tension with allowing you to match on the specific type
passed in is that, if you actually need to do this, then that means your
function can't handle certain type parameters which are permitted by its
signature, i.e. it's not total over its type parameter domain. If
interfaces allowed you to restrict the type parameters passed in to the
specific ones you can handle, then you would necessarily also be able to
handle each specific case by matching on interface satisfaction as well.

On Tue, Jul 7, 2020 at 10:44 AM roger peppe  wrote:

> On Tue, 7 Jul 2020 at 10:36, Tobias Gustafsson <
> tobias.l.gustafs...@gmail.com> wrote:
>
>> Hi all,
>>
>> Thanks for the response on this subject!
>>
>> Yes, a kind of type switch over generic types is probably what I
>> envision. There is a potentially nice symmetry with the current runtime
>> type switches and type annotations that caught my eye (being aware of the
>> fact that finding false patterns and symmetries may lead you astray...).
>>
>> If we do manage to set the semantics for a type switch over a generic
>> type I'd expect that to also spill over to type assertions over generic
>> types. Perhaps it would also be easier to start with at type assertion to
>> explore the problem space a bit more before heading directly for the type
>> switch.
>>
>
> That's not technically needed AFAICS, given that you can already convert
> to interface{} and then type switch, but it's potentially nice as syntax
> sugar.
>
>
>> I had not given that much thought into if the type switch should be over
>> the variable or the type parameter itself. Doing it over the type parameter
>> definitely implicates a refinement of the previously set type constraints
>> while doing the switch over the variable could allow a more liberal
>> interpretation where additional types/behaviour is tested for that goes
>> outside of what the type constraints specifies (it becomes a "local" type
>> constraint if you like). For the particular use case I have right now (a
>> specialized hash function) doing it as a refinement of the type constraints
>> is enough.
>>
>
> In my description, there's no assumption that doing it over the type
> parameter implies a refinement of the previously set type constraints. In
> fact it definitely implies otherwise because (for example) if you know that
> a generic type parameter has the known type "string", then it's a loosening
> rather than a tightening of the constraint - you can now do more things
> with values of that type than you could previously.
>
> I think there are plenty of examples in existing code bases where type
>> 

Re: [go-nuts] [generics] Issues with identifying the matched predeclared type

2020-07-07 Thread roger peppe
On Tue, 7 Jul 2020 at 21:05, Tobias Gustafsson <
tobias.l.gustafs...@gmail.com> wrote:

> Den tisdag 7 juli 2020 kl. 16:45:21 UTC+2 skrev rog:
>>
>> On Tue, 7 Jul 2020 at 10:36, Tobias Gustafsson 
>> wrote:
>>
>>> Hi all,
>>>
>>> Thanks for the response on this subject!
>>>
>>> Yes, a kind of type switch over generic types is probably what I
>>> envision. There is a potentially nice symmetry with the current runtime
>>> type switches and type annotations that caught my eye (being aware of the
>>> fact that finding false patterns and symmetries may lead you astray...).
>>>
>>> If we do manage to set the semantics for a type switch over a generic
>>> type I'd expect that to also spill over to type assertions over generic
>>> types. Perhaps it would also be easier to start with at type assertion to
>>> explore the problem space a bit more before heading directly for the type
>>> switch.
>>>
>>
>> That's not technically needed AFAICS, given that you can already convert
>> to interface{} and then type switch, but it's potentially nice as syntax
>> sugar.
>>
>
> Not sure I completely understand what you mean by this, what I meant is
> basically that I would find it good to be able to do things like this:
>
> var x T  // Generic type T
> s := x.(string)  // s is a string or compile time error
>
> s, ok := x.(string) // s is the string x represents if T is of type string,
> // otherwise a zero value string.
> // Any code relying on ok to be true can be eliminated
> // during compilation if type assertion fails and vice
> // versa.
>

By  "convert to interface{}", I meant using "interface{}(x)" to convert to
a type-switchable value. For example:
https://go2goplay.golang.org/p/txdvJauonEu
It might be nice to be able to do that without the conversion to
interface{}, but it's not technically *necessary*.

  cheers,
rog.

-- 
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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CAJhgachiRNc1X0oZW0BATuL7Y1uoWMmmKm5bzhiVpCKW77PgYQ%40mail.gmail.com.


Re: [go-nuts] [generics] Issues with identifying the matched predeclared type

2020-07-07 Thread Tobias Gustafsson


Den tisdag 7 juli 2020 kl. 16:45:21 UTC+2 skrev rog:
>
> On Tue, 7 Jul 2020 at 10:36, Tobias Gustafsson  > wrote:
>
>> Hi all,
>>
>> Thanks for the response on this subject!
>>
>> Yes, a kind of type switch over generic types is probably what I 
>> envision. There is a potentially nice symmetry with the current runtime 
>> type switches and type annotations that caught my eye (being aware of the 
>> fact that finding false patterns and symmetries may lead you astray...).
>>
>> If we do manage to set the semantics for a type switch over a generic 
>> type I'd expect that to also spill over to type assertions over generic 
>> types. Perhaps it would also be easier to start with at type assertion to 
>> explore the problem space a bit more before heading directly for the type 
>> switch.
>>
>
> That's not technically needed AFAICS, given that you can already convert 
> to interface{} and then type switch, but it's potentially nice as syntax 
> sugar.
>

Not sure I completely understand what you mean by this, what I meant is 
basically that I would find it good to be able to do things like this:

var x T  // Generic type T
s := x.(string)  // s is a string or compile time error

s, ok := x.(string) // s is the string x represents if T is of type string,
// otherwise a zero value string.
// Any code relying on ok to be true can be eliminated
// during compilation if type assertion fails and vice
// versa. 

>  
>
>> I had not given that much thought into if the type switch should be over 
>> the variable or the type parameter itself. Doing it over the type parameter 
>> definitely implicates a refinement of the previously set type constraints 
>> while doing the switch over the variable could allow a more liberal 
>> interpretation where additional types/behaviour is tested for that goes 
>> outside of what the type constraints specifies (it becomes a "local" type 
>> constraint if you like). For the particular use case I have right now (a 
>> specialized hash function) doing it as a refinement of the type constraints 
>> is enough.
>>
>
> In my description, there's no assumption that doing it over the type 
> parameter implies a refinement of the previously set type constraints. In 
> fact it definitely implies otherwise because (for example) if you know that 
> a generic type parameter has the known type "string", then it's a loosening 
> rather than a tightening of the constraint - you can now do more things 
> with values of that type than you could previously.
>

Yes, I realized shortly after sending the mail that it was probably not 
what you meant. I misinterpreted type refinement in this context and was 
probably also a bit primed by the suggested restrictions for type switches 
in the generics draft. Having realized this my (somewhat confusing) comment 
about switching over types and variables can also be ignored. Switching 
over the type parameter is nice and clean I think.
 

>
> I think there are plenty of examples in existing code bases where type 
>> switches/assertions are used to provide additional functionality for types 
>> that implement specific behaviours (http.Pusher/http.ResponseWriter comes 
>> to mind for example). Not restricting the type switch to be a refinement of 
>> the constraint types would allow for backwards compatible evolution of 
>> libraries, etc. to provide additional functionality for types that support 
>> it.
>>
>
> Yes, this would indeed be possible with my suggestion above. I quite like 
> its symmetry with the way that normal type switches work. 
>
> When it comes to exact vs. non exact matches I would have thought that the 
>> same rules that apply for todays type switches (eg. first match wins) would 
>> be enough but perhaps that is not the case in which case I think the "type" 
>> notation introduced above looks good. Ideally I would also like "MyInt" to 
>> match both "MyInt" and int with the order of the cases in the type switch 
>> deciding which case will be triggered. The compiler or external linters 
>> could warn/error on unreachable cases.
>>
>
> I don't think it's sufficient. Consider this program: 
> https://go2goplay.golang.org/p/wO0JHIuHH2l. If "string" matches both 
> "myString" and "string" itself, then the type checker would allow the 
> assignment of a function of type "func(string) uint64" to a function of 
> type "func(myString) uint64" with no explicit type conversion, which breaks 
> an important safety constraint in the language. Worse, if we let an 
> interface type match any concrete type that happens to implement that 
> interface, then there's a serious issue because those types probably don't 
> have the same shape, so can't be used interchangeably. For example, this 
> program would be valid, but isn't possible to compile because io.Stringer 
> and string have different representations: 
> https://go2goplay.golang.org/p/_uLjQxb-z-b
>

Great examples illustrating 

Re: [go-nuts] [generics] Issues with identifying the matched predeclared type

2020-07-07 Thread roger peppe
On Tue, 7 Jul 2020 at 10:36, Tobias Gustafsson <
tobias.l.gustafs...@gmail.com> wrote:

> Hi all,
>
> Thanks for the response on this subject!
>
> Yes, a kind of type switch over generic types is probably what I envision.
> There is a potentially nice symmetry with the current runtime type switches
> and type annotations that caught my eye (being aware of the fact that
> finding false patterns and symmetries may lead you astray...).
>
> If we do manage to set the semantics for a type switch over a generic type
> I'd expect that to also spill over to type assertions over generic types.
> Perhaps it would also be easier to start with at type assertion to explore
> the problem space a bit more before heading directly for the type switch.
>

That's not technically needed AFAICS, given that you can already convert to
interface{} and then type switch, but it's potentially nice as syntax sugar.


> I had not given that much thought into if the type switch should be over
> the variable or the type parameter itself. Doing it over the type parameter
> definitely implicates a refinement of the previously set type constraints
> while doing the switch over the variable could allow a more liberal
> interpretation where additional types/behaviour is tested for that goes
> outside of what the type constraints specifies (it becomes a "local" type
> constraint if you like). For the particular use case I have right now (a
> specialized hash function) doing it as a refinement of the type constraints
> is enough.
>

In my description, there's no assumption that doing it over the type
parameter implies a refinement of the previously set type constraints. In
fact it definitely implies otherwise because (for example) if you know that
a generic type parameter has the known type "string", then it's a loosening
rather than a tightening of the constraint - you can now do more things
with values of that type than you could previously.

I think there are plenty of examples in existing code bases where type
> switches/assertions are used to provide additional functionality for types
> that implement specific behaviours (http.Pusher/http.ResponseWriter comes
> to mind for example). Not restricting the type switch to be a refinement of
> the constraint types would allow for backwards compatible evolution of
> libraries, etc. to provide additional functionality for types that support
> it.
>

Yes, this would indeed be possible with my suggestion above. I quite like
its symmetry with the way that normal type switches work.

When it comes to exact vs. non exact matches I would have thought that the
> same rules that apply for todays type switches (eg. first match wins) would
> be enough but perhaps that is not the case in which case I think the "type"
> notation introduced above looks good. Ideally I would also like "MyInt" to
> match both "MyInt" and int with the order of the cases in the type switch
> deciding which case will be triggered. The compiler or external linters
> could warn/error on unreachable cases.
>

I don't think it's sufficient. Consider this program:
https://go2goplay.golang.org/p/wO0JHIuHH2l. If "string" matches both
"myString" and "string" itself, then the type checker would allow the
assignment of a function of type "func(string) uint64" to a function of
type "func(myString) uint64" with no explicit type conversion, which breaks
an important safety constraint in the language. Worse, if we let an
interface type match any concrete type that happens to implement that
interface, then there's a serious issue because those types probably don't
have the same shape, so can't be used interchangeably. For example, this
program would be valid, but isn't possible to compile because io.Stringer
and string have different representations:
https://go2goplay.golang.org/p/_uLjQxb-z-b

Matching over a list of types/variables seems like a potentially useful
> thing to do.
>

Thanks for the feedback.

  cheers,
rog.


>
> Thanks
> // Tobias
>
>
> Den tisdag 7 juli 2020 kl. 08:35:47 UTC+2 skrev rog:
>>
>> How about something like this?
>>
>> Type switches
>>
>> A *generic type switch* allows a generic function to provide specialized
>> behaviour based on its type arguments (for example to use a more efficient
>> implementation for some types).
>>
>> A type switch refines the type of a type parameter. Cases match actual
>> types against the generic type parameter in turn. Within the body of a
>> case, the named type parameter has the actual type. A single case may not
>> list more than one type.
>>
>> By default, matching is exact: for a case to match, the type parameter
>> must be exactly the specified type. By wrapping the specified type in a
>> type qualifier, the matching is not exact, but *matches* the kind of
>> type instead. For some non-interface type X, type(X) matches any type
>> with an underlying type of X. When X is an interface type, type(X) matches
>> any type that implements the interface. Type-list interfaces are 

Re: [go-nuts] [generics] Issues with identifying the matched predeclared type

2020-07-07 Thread Tobias Gustafsson
Hi all,

Thanks for the response on this subject!

Yes, a kind of type switch over generic types is probably what I envision. 
There is a potentially nice symmetry with the current runtime type switches 
and type annotations that caught my eye (being aware of the fact that 
finding false patterns and symmetries may lead you astray...).

If we do manage to set the semantics for a type switch over a generic type 
I'd expect that to also spill over to type assertions over generic types. 
Perhaps it would also be easier to start with at type assertion to explore 
the problem space a bit more before heading directly for the type switch.

I had not given that much thought into if the type switch should be over 
the variable or the type parameter itself. Doing it over the type parameter 
definitely implicates a refinement of the previously set type constraints 
while doing the switch over the variable could allow a more liberal 
interpretation where additional types/behaviour is tested for that goes 
outside of what the type constraints specifies (it becomes a "local" type 
constraint if you like). For the particular use case I have right now (a 
specialized hash function) doing it as a refinement of the type constraints 
is enough.
I think there are plenty of examples in existing code bases where type 
switches/assertions are used to provide additional functionality for types 
that implement specific behaviours (http.Pusher/http.ResponseWriter comes 
to mind for example). Not restricting the type switch to be a refinement of 
the constraint types would allow for backwards compatible evolution of 
libraries, etc. to provide additional functionality for types that support 
it.

When it comes to exact vs. non exact matches I would have thought that the 
same rules that apply for todays type switches (eg. first match wins) would 
be enough but perhaps that is not the case in which case I think the "type" 
notation introduced above looks good. Ideally I would also like "MyInt" to 
match both "MyInt" and int with the order of the cases in the type switch 
deciding which case will be triggered. The compiler or external linters 
could warn/error on unreachable cases.

Matching over a list of types/variables seems like a potentially useful 
thing to do.

Thanks
// Tobias


Den tisdag 7 juli 2020 kl. 08:35:47 UTC+2 skrev rog:
>
> How about something like this?
>
> Type switches
>
> A *generic type switch* allows a generic function to provide specialized 
> behaviour based on its type arguments (for example to use a more efficient 
> implementation for some types).
>
> A type switch refines the type of a type parameter. Cases match actual 
> types against the generic type parameter in turn. Within the body of a 
> case, the named type parameter has the actual type. A single case may not 
> list more than one type.
>
> By default, matching is exact: for a case to match, the type parameter 
> must be exactly the specified type. By wrapping the specified type in a 
> type qualifier, the matching is not exact, but *matches* the kind of type 
> instead. For some non-interface type X, type(X) matches any type with an 
> underlying type of X. When X is an interface type, type(X) matches any 
> type that implements the interface. Type-list interfaces are allowed.
>
> func F(type T, U)() {
>   switch T {
>   case int:
>   // T is exactly the type int.
>   case error:
>   // T is exactly error, not some type that happens to satisfy 
> the error inferface.
>   case type(string):
>   // T is any type with an underlying type of string
>   case type(io.ReadCloser):
>   // T is any type that satisfies the io.ReadCloser interface.
>   case type(interface{
>   type int, int64
>   }):
>   // T has an underlying type of either int or int64.
>   case string:
>   // Compile error: duplicate case (matched by generic(string) 
> above).
>   case type(io.Reader):
>   // Compile error: duplicate case (matched by 
> generic(io.ReadCloser) above).
>   case U:
>   // Both type parameters are exactly the same type.
>   }
> }
>
>
> *Discussion*
>
> It might be a good idea to allow a type switch to specify a list of types 
> rather than a single type, so several generic parameters can be specialized 
> at the same time. For example:
>
> switch T, U {
> case int, string:
> }
>
>
> This is syntactically rather more useful than in a normal type switch, 
> because there's no way to avoid nesting type switches (in a normal type 
> switch, it's possible to assign the specialized value to another variable 
> with wider scope, but that's not possible in general with generic type 
> switches). However, it may be too confusing syntactically with the normal 
> comma-list in a type switch statement which means "any of these types". A 
> possible way of avoiding confusion that might be to require brackets:
>
> switch 

Re: [go-nuts] [generics] Issues with identifying the matched predeclared type

2020-07-07 Thread roger peppe
How about something like this?

Type switches

A *generic type switch* allows a generic function to provide specialized
behaviour based on its type arguments (for example to use a more efficient
implementation for some types).

A type switch refines the type of a type parameter. Cases match actual
types against the generic type parameter in turn. Within the body of a
case, the named type parameter has the actual type. A single case may not
list more than one type.

By default, matching is exact: for a case to match, the type parameter must
be exactly the specified type. By wrapping the specified type in a
type qualifier,
the matching is not exact, but *matches* the kind of type instead. For some
non-interface type X, type(X) matches any type with an underlying type of X.
When X is an interface type, type(X) matches any type that implements the
interface. Type-list interfaces are allowed.

func F(type T, U)() {
switch T {
case int:
// T is exactly the type int.
case error:
// T is exactly error, not some type that happens to satisfy the
error inferface.
case type(string):
// T is any type with an underlying type of string
case type(io.ReadCloser):
// T is any type that satisfies the io.ReadCloser interface.
case type(interface{
type int, int64
}):
// T has an underlying type of either int or int64.
case string:
// Compile error: duplicate case (matched by generic(string) 
above).
case type(io.Reader):
// Compile error: duplicate case (matched by 
generic(io.ReadCloser) above).
case U:
// Both type parameters are exactly the same type.
}
}


*Discussion*

It might be a good idea to allow a type switch to specify a list of types
rather than a single type, so several generic parameters can be specialized
at the same time. For example:

switch T, U {
case int, string:
}


This is syntactically rather more useful than in a normal type switch,
because there's no way to avoid nesting type switches (in a normal type
switch, it's possible to assign the specialized value to another variable
with wider scope, but that's not possible in general with generic type
switches). However, it may be too confusing syntactically with the normal
comma-list in a type switch statement which means "any of these types". A
possible way of avoiding confusion that might be to require brackets:

switch T, U {
case (int, string):
}


The ability to use a concrete type directly in a  type(X) case is just
syntax sugar for type(interface{type X}) and could be omitted. It depends
how commonly used this might be.


On Tue, 7 Jul 2020 at 04:51, Steven Blenkinsop  wrote:

> On Mon, Jul 6, 2020 at 6:29 PM, roger peppe  wrote:
>
>>
>> I've also been playing around in this area. I've been trying something
>> similar to this approach: https://go2goplay.golang.org/p/sHko_EMhJjA
>> But this isn't ideal - note that we lose type safety when assigning back
>> to the generic hash function, because there's no way to let the compiler
>> know that the actual type of the generic type parameter is known at that
>> point.
>>
>> I also noticed an interesting wrinkle to doing a type switch on a value
>> of the type:
>>
>> We can't do this:
>>
>>   case Hasher:
>>   hi = func(t Hasher) uintptr {
>>   return t
>>   }
>>
>> because the actual type probably isn't Hasher - it's the actual type, not
>> the interface type.
>>
>> If a type switch on a type argument was implemented (and I definitely
>> support it) we'd have to think carefully about what semantics would be
>> desired in this case - if one of the types is an interface type, would one
>> get the usual interface-subtype rules for matching or not?
>>
>> For the record, I quite like the idea of using a separate syntax for
>> type-based checking:
>>
>>type switch T {
>>case int:
>>case string:
>>}
>>
>> That way, we could use exact-match rules for that construct without risk
>> of confusion with the normal type switch rules, and perhaps the difference
>> might make it clearer that T would change its type inside the body of the
>> switch.
>>
> If the idea is to be able to refine the constraints on a type parameter,
> then you need to be able to match based on "T satisfies this interface".
> If you want to be able to match based on underlying type, you could match
> against e.g. interface { type int }.
>
> I'm not sure how useful matching on a precise type is in this context.
> Interfaces can't express exact type constraints as currently designed, so
> exact type matching can't ever handle all cases. You'd need a really good
> reason to be treating exactly one type out of the set with the same
> underlying type differently in order for it to make sense.
>
> If you needed to, though, you could plausibly do it by:
>
> type switch *T {
> case interface { 

Re: [go-nuts] [generics] Issues with identifying the matched predeclared type

2020-07-06 Thread Steven Blenkinsop
On Mon, Jul 6, 2020 at 6:29 PM, roger peppe  wrote:

>
> I've also been playing around in this area. I've been trying something
> similar to this approach: https://go2goplay.golang.org/p/sHko_EMhJjA
> But this isn't ideal - note that we lose type safety when assigning back
> to the generic hash function, because there's no way to let the compiler
> know that the actual type of the generic type parameter is known at that
> point.
>
> I also noticed an interesting wrinkle to doing a type switch on a value of
> the type:
>
> We can't do this:
>
>   case Hasher:
>   hi = func(t Hasher) uintptr {
>   return t
>   }
>
> because the actual type probably isn't Hasher - it's the actual type, not
> the interface type.
>
> If a type switch on a type argument was implemented (and I definitely
> support it) we'd have to think carefully about what semantics would be
> desired in this case - if one of the types is an interface type, would one
> get the usual interface-subtype rules for matching or not?
>
> For the record, I quite like the idea of using a separate syntax for
> type-based checking:
>
>type switch T {
>case int:
>case string:
>}
>
> That way, we could use exact-match rules for that construct without risk
> of confusion with the normal type switch rules, and perhaps the difference
> might make it clearer that T would change its type inside the body of the
> switch.
>
If the idea is to be able to refine the constraints on a type parameter,
then you need to be able to match based on "T satisfies this interface". If
you want to be able to match based on underlying type, you could match
against e.g. interface { type int }.

I'm not sure how useful matching on a precise type is in this context.
Interfaces can't express exact type constraints as currently designed, so
exact type matching can't ever handle all cases. You'd need a really good
reason to be treating exactly one type out of the set with the same
underlying type differently in order for it to make sense.

If you needed to, though, you could plausibly do it by:

type switch *T {
case interface { type *fmt.Stringer }:
// T has to be the interface type `fmt.Stringer`
case interface { type *int }:
// T has to be the specific concrete type `int`
}

Whether to support the following is what could potentially cause confusion:

func F(type T interface { type int })(t T) {
type switch T {
case int:
...
}
}

Someone could plausibly believe this covers all possible cases, when it
doesn't cover type MyInt int, for example. This could either be disallowed
or picked up by a lint. Disallowing it would force the above workaround
syntax using pointer types in interface type lists. This is a bit kludgey,
but then it probably shouldn't be easier to do the thing which will more
often be wrong than the thing that will more often be right.

The other thing is that interface constraints as currently designed don't
really lend themselves to refinement in the first place. Sure, if you have
an interface with a type list, you can match on a subset of that type list,
but you can't ask whether a type "is in this type list *or* satisfies this
interface" or "satisfies at least one interface in this list". Easiest
solution might be to allow comma separated embedded interfaces:

type CustomOrdered(type T) interface {
Less(T) bool
}

type AnyOrdered(type T) interface {
constraints.Ordered, (CustomOrdered(T))
}

(I should also mention I don't really *like* having to define the
above interfaces so they have to be applied reflexively to work. The
fact that it will be done automatically in type parameter lists helps,
but it still feels wrong to have to do it. I understand that this is
to sidestep needing to add a second kind of generic type name in
interface definitions, though.)

-- 
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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CANjmGJtp8osgT1xxPz-DdT9DF0c7-sWny2N9Ad60dFGLwUmadw%40mail.gmail.com.


Re: [go-nuts] [generics] Issues with identifying the matched predeclared type

2020-07-06 Thread roger peppe
On Mon, 6 Jul 2020 at 17:46,  wrote:

> Hi!
>
> I've spent some time lately to try out the go2go tool and the new generics
> proposal by converting a small hack I did some years ago for immutable data
> structures (https://github.com/tobgu/peds) which, in it's current shape,
> depends on code generation.
>
> There is nothing really mind bending to this since it's a pretty
> mainstream collections case of generics and overall the conversion process
> was quite pleasant. So, good job on the generics proposal so far!
>
> I did run up against one issue though that I've not really been able to
> cope with in an elegant, and decently performant, way so far. That of
> providing different implementations depending on the underlying type. For
> my particular case I would like to use differnt hash functions for the map
> depending on the type of the map key. In addition to this I would also like
> the user of the library to be able to provide their own hash function,
> should they want to, by implementing a Hasher interface.
>
> I've provided some example code to illustrate what I would like to be able
> to do here: https://go2goplay.golang.org/p/HQcSZj_nfaF
>
> I've read the discussion in the draft on the limitations related to this
> here:
>
> https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#identifying-the-matched-predeclared-type
>
>
> But from that it's not entirly clear to me if:
> - This is a fix limitation that has been set to prevent bad design,
> unintended behaviour (compile time turing completness, language
> inconsistencies, etc.) or if it's a issue that should/will be fixed before
> the final implementation?
> - The limitations presented in the last paragraph of the above linked
> document are there for soundness of the implementation or if there are
> technical reasons for them? To me they seem unnecessarily restrictive.
> Since a type switch over a parametrized type could be evaluated at compile
> time it should be possible to use it in a fully type safe manner while it
> would provide the same type of flexibility at compile time that the current
> type switch provides at runtime.
>
> I'm also open to the fact that there may be entirely differnt ways to go
> about solving my particular problem (in a clean and efficient manner)
> within the bounds of the current spec. If so I'd be super happy to take
> suggestions!
>

I've also been playing around in this area. I've been trying something
similar to this approach: https://go2goplay.golang.org/p/sHko_EMhJjA
But this isn't ideal - note that we lose type safety when assigning back to
the generic hash function, because there's no way to let the compiler know
that the actual type of the generic type parameter is known at that point.

I also noticed an interesting wrinkle to doing a type switch on a value of
the type:

We can't do this:

  case Hasher:
  hi = func(t Hasher) uintptr {
  return t
  }

because the actual type probably isn't Hasher - it's the actual type, not
the interface type.

If a type switch on a type argument was implemented (and I definitely
support it) we'd have to think carefully about what semantics would be
desired in this case - if one of the types is an interface type, would one
get the usual interface-subtype rules for matching or not?

For the record, I quite like the idea of using a separate syntax for
type-based checking:

   type switch T {
   case int:
   case string:
   }

That way, we could use exact-match rules for that construct without risk of
confusion with the normal type switch rules, and perhaps the difference
might make it clearer that T would change its type inside the body of the
switch.

  cheers,
rog.

>
> Thanks a lot!
> // Tobias
>
> --
> 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+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/golang-nuts/1e5a294f-ea3a-42a5-97ed-32471a2ed9a6o%40googlegroups.com
> 
> .
>

-- 
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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CAJhgacgHKMoKNhoWs%3DF_LdpELVNsA3kT3UbXitxG4do%2B41FUPA%40mail.gmail.com.


Re: [go-nuts] [generics] Issues with identifying the matched predeclared type

2020-07-06 Thread Ian Lance Taylor
On Mon, Jul 6, 2020 at 9:46 AM  wrote:
>
> I've spent some time lately to try out the go2go tool and the new generics 
> proposal by converting a small hack I did some years ago for immutable data
> structures (https://github.com/tobgu/peds) which, in it's current shape, 
> depends on code generation.
>
> There is nothing really mind bending to this since it's a pretty mainstream 
> collections case of generics and overall the conversion process was quite 
> pleasant. So, good job on the generics proposal so far!
>
> I did run up against one issue though that I've not really been able to cope 
> with in an elegant, and decently performant, way so far. That of providing 
> different implementations depending on the underlying type. For my particular 
> case I would like to use differnt hash functions for the map depending on the 
> type of the map key. In addition to this I would also like the user of the 
> library to be able to provide their own hash function, should they want to, 
> by implementing a Hasher interface.
>
> I've provided some example code to illustrate what I would like to be able to 
> do here: https://go2goplay.golang.org/p/HQcSZj_nfaF
>
> I've read the discussion in the draft on the limitations related to this here:
> https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#identifying-the-matched-predeclared-type
>
> But from that it's not entirly clear to me if:
> - This is a fix limitation that has been set to prevent bad design, 
> unintended behaviour (compile time turing completness, language 
> inconsistencies, etc.) or if it's a issue that should/will be fixed before 
> the final implementation?
> - The limitations presented in the last paragraph of the above linked 
> document are there for soundness of the implementation or if there are 
> technical reasons for them? To me they seem unnecessarily restrictive. Since 
> a type switch over a parametrized type could be evaluated at compile time it 
> should be possible to use it in a fully type safe manner while it would 
> provide the same type of flexibility at compile time that the current type 
> switch provides at runtime.
>
> I'm also open to the fact that there may be entirely differnt ways to go 
> about solving my particular problem (in a clean and efficient manner) within 
> the bounds of the current spec. If so I'd be super happy to take suggestions!

Thanks for trying it out.

I think that we haven't really figured out whether there should be a
way to do a type switch on a type argument.  It's not a technical
limitation.  We're just not sure what to do here.  It sounds like you
are in favor of supporting a type switch on a type argument.

Ian

-- 
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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CAOyqgcVD5uPtNpGC7nKetTK3SX_P%2Be8PgxavchcZ31A%2BXL39FA%40mail.gmail.com.


[go-nuts] [generics] Issues with identifying the matched predeclared type

2020-07-06 Thread tobias . l . gustafsson
Hi!

I've spent some time lately to try out the go2go tool and the new generics 
proposal by converting a small hack I did some years ago for immutable data
structures (https://github.com/tobgu/peds) which, in it's current shape, 
depends on code generation.

There is nothing really mind bending to this since it's a pretty mainstream 
collections case of generics and overall the conversion process was quite 
pleasant. So, good job on the generics proposal so far!

I did run up against one issue though that I've not really been able to 
cope with in an elegant, and decently performant, way so far. That of 
providing different implementations depending on the underlying type. For 
my particular case I would like to use differnt hash functions for the map 
depending on the type of the map key. In addition to this I would also like 
the user of the library to be able to provide their own hash function, 
should they want to, by implementing a Hasher interface.

I've provided some example code to illustrate what I would like to be able 
to do here: https://go2goplay.golang.org/p/HQcSZj_nfaF

I've read the discussion in the draft on the limitations related to this 
here:
https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#identifying-the-matched-predeclared-type
 

But from that it's not entirly clear to me if:
- This is a fix limitation that has been set to prevent bad design, 
unintended behaviour (compile time turing completness, language 
inconsistencies, etc.) or if it's a issue that should/will be fixed before 
the final implementation?
- The limitations presented in the last paragraph of the above linked 
document are there for soundness of the implementation or if there are 
technical reasons for them? To me they seem unnecessarily restrictive. 
Since a type switch over a parametrized type could be evaluated at compile 
time it should be possible to use it in a fully type safe manner while it 
would provide the same type of flexibility at compile time that the current 
type switch provides at runtime.

I'm also open to the fact that there may be entirely differnt ways to go 
about solving my particular problem (in a clean and efficient manner) 
within the bounds of the current spec. If so I'd be super happy to take 
suggestions!

Thanks a lot!
// Tobias

-- 
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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/1e5a294f-ea3a-42a5-97ed-32471a2ed9a6o%40googlegroups.com.