On Wed, Jul 8, 2020 at 3:59 AM roger peppe <rogpe...@gmail.com> 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 <rogpe...@gmail.com> wrote:

>
>
> On Wed, 8 Jul 2020 at 00:33, Steven Blenkinsop <steven...@gmail.com>
> wrote:
>
>> On Tue, Jul 7, 2020 at 10:44 AM, roger peppe <rogpe...@gmail.com> 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 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/CANjmGJuiBw95%3DGOxRgwdYGzkCH8RS_oJKLhdjqWoz1kPEYTh8Q%40mail.gmail.com.

Reply via email to