Rust uses "extends" *and* a colon? (I think I get it -- it uses the colon but you can also use a "where" clause.)
 
Yeah in Rust `fn fun<T: Sized>(a: T) {}` is syntactic sugar over

fn fun<T>(a: T)
where
    T: Sized
{}

where clauses are actually incredibly powerful, but as Eric points out, they probably wouldn't work well for Python.

class Example[T] # () would go here??
where
    T: int:
    ...

def foo[B](b: B) -> B
where
    B: int:
    ...

Though now that I've written these examples this actually doesn't look too terrible? Maybe I just have used Rust too much recently :)


Anyway, to the issue at hand, I expect people will be confused if we do T(int), for example, consider this declaration:

class Bar[T(int)](Baz[T]): ...

That's a *lot* of brackets and parentheses in a small amount of space, and it makes it harder to find an unbalanced bracket or parenthesis.

I also don't particularly like <:. While it is used in type theory papers and Scala, most people that use types have read neither of those, and it will be unfamiliar.

I think the utility of lower bounds is probably unlikely to be worth their introduction (though perhaps someone has a use case I haven't thought of!), so I feel that the colon syntax is the best candidate: it is familiar, and it is the easiest to read, which is of course a matter of opinion, but perhaps it would be good to do a poll about this?


On Sat, Jul 2, 2022 at 7:38 PM Guido van Rossum <guido@python.org> wrote:
On Fri, Jul 1, 2022 at 7:20 PM Eric Traut <eric@traut.com> wrote:
> It's a bit different though in that the type parameter's bound is not the type of the variable

I don't think it's different. When you annotate a function parameter with `Foo`, you are not saying that the type must be a `Foo` object at runtime. Rather, you're saying that the parameter is constrained to a type that is compatible with `Foo` — i.e. a subtype thereof.

It's the same logic with the upper bound for a type parameter. When you say that it is bounded by `Foo`, you are not saying that it is type `Foo`, but rather that its type is constrained by `Foo` — i.e. it must be a subtype thereof.

I think Jelle is talking about the type of the thing before the colon in relation to the thing after the colon. When you say 'T: int' in a type parameter clause, that means roughly 'issubclass(T, int)'. But when you write 'a: int' in a parameter list, that means 'isinstance(a, int)'.

However, I'm not convinced this is a big enough problem to reject ':' outright.
 
I'm not completely opposed to using `<:` like in Scala, but I don't think most Python users will have ever seen (or heard of) Scala, so this token will seem very foreign to them. Also, I don't think it works well with constrained type parameters.

I've seen this used in papers about static types as well, without introduction, so apparently it's a standard operator in that community ("Consider types *T* and *S* s.t. *T* <: *S* ... etc.). However, it's always taken me a bit of thinking about the context to derive that '<:' means "subclass" (i.e., "extends") and not "superclass".
 
To inform this discussion, I've updated the draft PEP to include an "Appendix A: Survey of Type Parameter Syntax". Here's a direct link: https://github.com/erictraut/peps/blob/typeparams/pep-9999.rst#appendix-a-survey-of-type-parameter-syntax

That link no longer works (probably because I merged the PR :-). Here's a working link: https://github.com/python/peps/blob/main/pep-0695.rst#appendix-a-survey-of-type-parameter-syntax
 
I looked at C++, Java, C#, TypeScript, Scala, Swift, Rust, Kotlin, and Julia.

There are four patterns that emerge for specifying constraints on a type parameter:
1. Java and TypeScript use the "extends" keyword
2. C# and Rust use the "where" keyword (and place the clause at the end of the declaration — something that wouldn't work well for Python's grammar)
3. Scala and Julia use the "<:" operator
4. Swift, Rust and Kotlin use a colon

Rust uses "extends" *and* a colon? (I think I get it -- it uses the colon but you can also use a "where" clause.)
 
If we think that we will want to support a lower bound at some point in the future, that would argue in favor of "<:" because ">:" is the logical counterpart. However, I don't think there's a strong need for specifying a lower bound, and most languages forego this capability. For that reason, I still favor using a colon here.

The call-like syntax also easily accommodates this: just add a new keyword parameter 'lower_bound=...'.
 
Note that none of these languages use a function call or constructor-like syntax. Adopting that approach would make Python an outlier among all other popular languages.

Not that we care all that much about that (none of them use indentation either :-).

--
--Guido van Rossum (python.org/~guido)
_______________________________________________
Typing-sig mailing list -- typing-sig@python.org
To unsubscribe send an email to typing-sig-leave@python.org
https://mail.python.org/mailman3/lists/typing-sig.python.org/
Member address: ethan@ethanhs.me