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 

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