Sacrificing the possibility of a typed lambda function to achieve a clean and capable callable signature sounds like a good compromise to make. +1
I wouldn’t mind def(x: int) -> int myself either since it means that support for a typed lambda is then potentially possible with lambda(): ..., but def does read like “define” to me (who speaks zero Dutch) so I understand why Guido dislikes the use of def as a noun.
Unfortunately a soft keyword that acts like a function can’t work because it would be ambiguous as to whether fn() should resolve to a keyword or a regular name.
How about we make Callable a soft keyword that resolves into a keyword if and only if it is followed by parentheses and an arrow? The arrow is then mandatory for typing a signature:
Callable(a: int, b: str) -> Any # typing signature
Callable(a, b) # calling Callable
I prefer Callable as a soft keyword over callable because Callable is currently used with square brackets for typing a callable so there aren’t nearly as many existing codes that call Callable as a callable compared to callable.
This will require backtracking to fall back from Callable() -> ... to Callable() but since there aren’t many existing codes (only 10.6k matches on GitHub) that call Callable() I don’t think backtracking will occur often enough to cause performance concerns.
This syntax works well IMO. And on further thoughts, I am beginning to dislike def as well; definition should not be an expression. Although it would be nice to find something shorter than lambda, I am ok with this compromise.
But these are the scenarios when I really wish we had PEP 638 macros. Hopefully it could enable something like: fn!(x: int, y: str -> str).
It is definitely no beauty. With InlinedDicts PEP it would be the just the natural consequence that any variable TD of a TypedDict can be replaced with TypedDict[{...}].
I am indifferent towards a **TD unpacking and for the same reason as PEP 692 I do not really want to propose it.
It would add one more use-case to motivate a possible Unpack replaced with */** to the discussion.
Maybe to summarize, I think would be unexpected that type C[*Ts] = Callable[[int, *Ts, Unpack[TD]], Any] is accepted syntax but: type C[*Ts] = Callable[[int, *Ts, **TD], Any] isn’t.
Alternatively type C[*Ts] = Callable[[int, *Ts, *TD], Any] would be a middle ground that does not require changes to the syntax that should be doable without much overhead.
Yes there are some ideas that go in that direction, in fact I started a discussion about this as well here, a larger one was made before here as well.
I remember at least one more, likely linked in the later one. There are quite a few flavors that exist for that idea.
I think the discussion should be split as it is two different topics and goals Unpack[TD] in Callable is something that should be doable more easily without much needed changes. Reviving PEP 677 would be the long run for something completely new.
First of all thank your very for the comprehensive reply.
I figured that the keyword in between ParamSpec problem would come flying around again. Its again been a while since I read about it. I know you posted a great link somewhere, but I could not find it again. Maybe it lead to the respective PEP discussion here?
This point might best discussed separately?
Right, without closed=True, any extra items in kwargs are allowed.
Nice catch. It seems to be more typed like Any by mypy or rather Unknown when speaking in pyright terms. I took a glance on their PR and didn’t see any special handling, e.g. fixing it to Any.
Yes I think positional arguments should be allowed as well. I’ll do a revisit on my first post soon. A TypeVarTuple looks fine to me as well. Anything else afterwards should be prohibited, maybe expect further Unpack[TD2]s and maybe maybe a ParamSpec.
You do not mean Unpack[T], just like in your snipper right? Unpacking a generic TypedDict should be allowed. Should I mention this, it feels like a given to me.
For multiple TypedDicts [Unpack[TD1], Unpack[TD2]] it might be the most reasonable to treat it like an implicit subclass class TD(TD2, TD1) that is then unpacked - and raise errors like when creating that subclass. This is likely the safest options at the cost of intended overwriting in case of a key conflict.
However, as such a decision would lock us in, I might be wiser to not allow multiple Unpack (for now). At the cost of flexibility, users can make a subclass with positional adjustments themselves.
However, I figure some still might favor a quick inline injection of an additional keywords if PEP 764 comes to be: [Unpack[ResuableTD], Unpack[TypedDict[{"new_keyword": int}]].
Just as a side note: Unpack[TD1, TD2] is theoretically still left open for future-use and a a possible re-interpretation, that could also be used for **kwargs typing as.
I am still very interested in hearing thoughts and opinions on this matter, if [Unpack[TD1], Unpack[TD2]] should be allowed and which how it should be potentially treated by a type-checker.