My brain was stuck on subtyping yesterday, and when I thought about how
subtyping relates to type-list interfaces I realized that they could be
made more orthogonal — and more like existing interface types, more useful
as sum types, and perhaps even more amenable to specialization via
type-switches — if we were to split the “underlying-type list interface
constraint” into three parts:

1. An interface type that is a list of other types. (Basically just a “sum
type” or disjunction.)
2. A parameterized interface type that expands a single type or type-list
interface to the set of types that have that type (or members of the type
list) as underlying types, and perhaps allows conversion of those types to
their underlying types
3. A non-type constraint concrete that restricts the type argument to only
non-interface types.

More detail in https://github.com/bcmills/go2go/blob/master/typelist.md.

On Mon, Jun 22, 2020 at 5:12 PM Bryan C. Mills <bcmi...@google.com> wrote:

> On Sat, Jun 20, 2020 at 5:25 PM Ian Lance Taylor <i...@golang.org> wrote:
>
>> On Fri, Jun 19, 2020 at 1:50 PM Bryan C. Mills <bcmi...@google.com>
>> wrote:
>> >
>> > On Fri, Jun 19, 2020 at 2:38 PM Ian Lance Taylor <i...@golang.org>
>> wrote:
>> >>
>> >> On Fri, Jun 19, 2020 at 9:31 AM Bryan C. Mills <bcmi...@google.com>
>> wrote:
>> >> >
>> >> > On Fri, Jun 19, 2020 at 1:30 AM Ian Lance Taylor <i...@golang.org>
>> wrote:
>> >> >>
>> >> >> This code is acting as though, if ordinary interface types could
>> have
>> >> >> type lists, it would be OK to write
>> >> >>
>> >> >> func Add2(x, y SmallInt) SmallInt { return x + y }ᵢ
>> >> >>
>> >> >> That is not OK, even though SmallInt has a type list.  Even though
>> >> >> every type in theSmallInt type list supports +, they don't support +
>> >> >> with every other type in the type list.
>> >> >
>> >> >
>> >> > Yes, that is exactly my point: the fact that that program is invalid
>> implies that type-list interfaces are not consistent with the semantics of
>> other interface types.
>> >>
>> >> I'm sure that I don't really understand what you are saying.
>> >>
>> >> But from my perspective using a type list in an interface type used as
>> >> a type constraint permits certain operations in that generic function.
>> >> That is a specific feature of generic functions.  You seem to be
>> >> trying to extend that capability to non-generic functions.  But that
>> >> doesn't work.
>> >
>> >
>> > I am not trying to extend that capability to non-generic functions. I
>> am pointing out that the fact that generic functions do have that
>> capability implies that type-list interfaces — unlike non-type-list
>> interfaces! — would have a meaning as type constraints that is incompatible
>> with their meaning as non-generic interface types.
>> >
>> > (Specifically, they would have the usual interface property that “T
>> implements T” except when used as type constraints, and as you note, that
>> exception is fundamental to the design of constraints.)
>>
>> I think that what you are saying is that if I write
>>
>> type C interface { ... }
>> func F(type T C) {}
>>
>> then if C does not have a type list then I can write
>>
>> F(C)
>>
>> because C as an interface type has all the methods that are required
>> by C as a constraint.  But if C has a type list then I can't write
>> that, because C itself is normally not a member of the type list.
>>
>> If that is what you are saying, then I agree.
>>
>> I guess the next question is why this matters.
>>
>
> I'm not sure that it does matter for functions with a single type argument.
> But it matters for functions such as `copy` and `append` that may need
> multiple interrelated arguments, such as an element type and a
> corresponding slice or channel type.
>
> >> > Because the rules for type-list interfaces always have an extra
>> condition beyond “simply implementing the interface type”, we would be
>> locked into at least one of the following limitations on the evolution of
>> the language:
>> >> >
>> >> > A type-list interface can never be used as a type argument.
>> >> > Or, a type parameter of an interface type can never be allowed as
>> the constraint of another type parameter, nor embedded in the constraint of
>> another type parameter.
>> >>
>> >> I don't understand what the second limitation means.  The current
>> >> design draft has no way to require that a type parameter have an
>> >> interface type.  Can you give an example of what you mean?
>> >
>> >
>> > https://go2goplay.golang.org/p/6cu23w3iYHQ
>>
>> Thanks.  Here I think you are suggesting that we should be able to use
>> a type parameter as a type constraint.  I don't agree.  It's
>> fundamental to this design that a generic function provides a contract
>> for its type arguments.  The constraints determine the permitted type
>> arguments, and they determine the operations permitted in the function
>> body.  If we use a type parameter as a type constraint, that means
>> that the contract is partially determined by the caller.  That serves
>> no purpose: the caller already controls the type arguments, so there
>> is no reason for the caller to constrain the type arguments to be
>> passed.
>
>
> The reason to constrain one type parameter by another is to force the
> caller to pass one type that implements (or is a subtype of) the other.
>
> And the generic function body can't determine anything from a
>> constraint defined by the caller, so it doesn't permit any additional
>> operations in the function body.
>
>
> The constraint that one parameter implements the actual type of another
> should allow the callee to assign variables of the one type to the other.
>
> That (at least partly) mitigates the lack of an explicit “assignable-to”
> constraint, since most of the assignable types people care about are
> conceptually subtypes (such as an interface and an implementation of that
> interface, or a directional channel and the corresponding undirected
> channel, or a defined function type and the corresponding literal function
> type).
>
> So I don't think we should permit
>> using a type parameter as a type constraint, even if we had some way
>> to require a type parameter to be an interface type.
>>
>>
>> >> > Otherwise, we would break the substitution property: the meaning of
>> a type-list interface would depend upon whether it was written literally in
>> the source code or passed as a type argument. (That is: the meaning of a
>> generic declaration instantiated with a given list of argument types would
>> be different from the meaning of the same declaration with some of its
>> actual arguments substituted for the corresponding parameters.)
>> >>
>> >> Again I'm sure that I don't understand what you are saying.  My
>> >> initial reaction is that of course the meaning of a type-list
>> >> interface depends on whether it is used as a type constraint or is
>> >> passed as a type argument.  Those are two entirely different
>> >> operations.  You can't use a type parameter as a type constraint.  I
>> >> suppose you could regard that as a limitation of the system, but it's
>> >> an intentional one.
>> >
>> >
>> > It is already possible to use a type parameter as part of a type
>> constraint, as illustrated by the SliceConstraint example.
>> > That pattern works in a variety of situations, just not as the entire
>> constraint or as an interface embedded in the constraint type (
>> https://go2goplay.golang.org/p/cYOhEKo-mm0).
>> >
>> > The design doc states clearly that “[t]ype constraints are interface
>> types”, so it seems oddly inconsistent to allow the parameter to be a
>> component of the constraint but not the entire constraint.
>>
>> Note that when we use a parameter as a component of the constraint we
>> are requiring type identity.  If we were to give a meaning to using a
>> parameter as a constraint, I think the only possible meaning would be
>> that the two parameters would be constrained to be identical types.  I
>> don't see a reason to use the value of the type argument.
>>
>
> I agree that it would not be very useful to constrain the two parameters
> to be identical types.
> Rather, such a constraint should force one parameter to “implement” (i.e.
> be a subtype of) the actual type argument passed for the other.
>
> For my previous example
> func Copy(type E interface{}, T E)(dst []E, src ...T) int
> that would mean that if E is an interface type, T must implement E.
>
> I considered this further, and realized that we could fairly easily
> generalize subtyping to non-interface types as well.
> (See https://github.com/bcmills/go2go/blob/master/subtypes.md for detail.)
>
> In general the meaning of constraints is entirely determined by the
>> function definition, and does not depend in any way on the type
>> arguments.
>
>
> How would correlating the type constraints change that property? We would
> still type-check the body of the function using (only) the declared
> constraints.
> For example, a function with the signature
>
> func LogAndAppend(type E fmt.Stringer, T E)(dst []E, src ...T) []E
>
> would be allowed to invoke the String method on variables of type T
> (because the constraint requires T to implement E, and E itself requires a
> String method), and would be able to append the elements of src to dst
> (because T implements, and is therefore assignable to, the type passed as
> E), but would not be able to append the elements of dst to src (because E
> is not constrained to implement T).
>
> The contract for the caller would be that both T and E must implement
> fmt.Stringer, and T must also implement any additional methods in E (so
> that T really is assignable to E).
>

-- 
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/CAKWVi_SRji8aGpPc8cdzotzYsjgGcihw6BZg61xFCOtNxpnb6g%40mail.gmail.com.

Reply via email to