You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I haven't found a way to make this work, does anyone know a trick or is it impossible?
If not, consider the syntax as a proposal.
Reference implementations would be Haskell, Scala.
optimally, the HK's type param would be indexable as well, allowing for F[X[X, X], X[X]]
Summary of current status (by @smheidrich, 2024-02-08):
@JelleZijlstrahas indicated interest in sponsoring a PEP, conditional on a prototype implementation in a major type checker and a well-specified draft PEP.
Drafting the PEP takes place in @nekitdev's fork of the peps repo. The stub PEP draft so far contains a few examples of the proposed syntax.
That same repo's GitHub Discussions forum forum has been designated as the place to discuss the PEP (and presumably the prototype implementation?). Some limited further discussions have taken place there.
If you want to be notified of new discussion threads, I think you have to set the whole repo as "watched" in GitHub?
Honestly I have no idea what higher-kinded type vars are -- my eyes glaze over when I hear that kind of talk. :-)
I have one more suggestion, then you're on your own. Use a common base class that has an Optional[Coroutine[...]] return type and derive both the regular Redis class and the RedisPipeline class from it.
I wonder if bidict provides a practical example of how this issue prevents expressing a type that you can actually imagine yourself needing.
>>>element_by_atomicnum=bidict({0: "hydrogen", 1: "helium"})
>>>reveal_type(element_by_atomicnum) # bidict[int, str]# So far so good, but now consider the inverse:>>>element_by_atomicnum.inversebidict({"hydrogen": 0, "helium": 1})
merely from a type hint that we could add to a super class. It would parameterize not just the key type and the value type, but also the self type. In other words, something like:
where $SELF_TYPE would of course use some actually legal syntax that allowed composing the self type with the other parameterized types.
Reacted by Janka Uryga, Anton Agestam, Sascha Desch, Joel Berkeley, Langston Barrett, laike9m, Mike McDermott, Misha Wolfson, Matthieu Bizien, Redowan Delowar and 15 more
This passes but IIUC you want the ABC to have a more powerful type. I guess here we might want to write it as
definverse(self: T) ->T[VT, KT]: # E: Type variable "T" used with arguments
Have I got that?
Reacted by Eric W., Mike McDermott, yoni-lavi, Redowan Delowar, Pradeep Kumar, lapraswastaken, Michael Scott Asato Cuthbert, Shahriar Heidrich, Anselm Schüler, Eugene Toder and 6 more
Exactly! It should be possible to e.g. subclass bidict (without overriding inverse), and have mypy realize that calling inverse on the subclass gives an instance of the subclass (with the key and value types swapped as well).
This isn’t only hypothetically useful, it’d really be useful in practice for the various subclasses in the bidict library where this actually happens (frozenbidict, OrderedBidict, etc.).
Glad this example was helpful! Please let me know if there’s anything further I can do to help here, and (can’t help myself) thanks for creating Python, it’s such a joy to use.
Reacted by ghurts, Mike McDermott, Dobatymo, Adrian Garcia Badaracco, Redowan Delowar, lapraswastaken, Michael Scott Asato Cuthbert, Shahriar Heidrich, Eugene Toder, Díaz-Esparza and 2 more
TLDR: So, instead of writing T[K] we can emulate this by using HKT[T, K] where HKT is a basic generic instance processed by a custom mypy plugin. I am working on this plugin for already some time now, but there's still nothing to show. You can track the progress here: https://pypi.org/project/kinds/ (part of dry-python libraries)
Reacted by Joshua Bronson, Peter Gaultney, Ivan Zakrevsky, Anton Agestam, Changsheng Wu, Carlos Florensa, Neil Girdhar, Mike McDermott, Dobatymo, Mathieu Pichette and 38 more
But I still don't get the connection with @samuelcolvin's RedisPipeline class. :-(
Sorry if I wasn't clear. I'll try again to explain:
I have a class with many (~200) methods, they all return coroutines with different result types (None, bytes, str, int or float). I want a subclass with the same internal logic but where all those methods return None - I can do this in python, but not with type hints currently (here's the actual code if it helps)
Except I don't want to have to redefine all the methods on Bar. Assuming I could create my own AlwaysNone type which even when parameterised told mypy the result would always be None
I'd be in the money, but quite understandably it can't.
I've currently got a working solution where i generate a .pyi stub file with definitions for all these methods but changing the return type to None (see here) so I'm not in immediate need of this anymore.
Hi @Kentzo, that's incorrect, these are not completely different signatures.
Please review the code here; as you can see these are functions which may return an awaitable object, not coroutines. This approach and similar, while not necessarily obvious to beginners, is relatively common in libraries which make extensive use of asyncio and the standard library asyncio code.
If you're not sure how it works, feel free to submit an issue on that project and I'll endeavour to explain it to you.
This is off-topic, please create an issue on async-redis if you want to discuss this more.
In short, yes they're different signatures, but I think my explanation above gives enough detail on what I'm doing and why I think it relates to this issue.
+1 on this. It's hard to say the use cases are rare when it's not even possible to express... There may be countless use cases hiding behind duck typing that simply can't be checked by the type system and therefore certainly aren't recognized as such. Having a type safe generic "Mappable" class as shown in the OP would be really useful.
Reacted by Sebastian Wagner, Mike McDermott, Harald Husum, Shahriar Heidrich, Pragy Agarwal and Alexander Schepanovski
It's hard to say the use cases are rare when it's not even possible to express
That makes no sense. You can express the OP's example just fine in untyped Python, you just can't express the types correctly. So if there were "countless" real examples we would encounter frequent user requests for type system extensions to allow typing this correctly. I haven't seen a lot of those.
@gvanrossum Inexpressible in the type system is what I was referring to. My point was that writing this off as rare seems premature since Python's types system is not a hard stop on your ability to keep going. So it might be actually quite common and how would we know? Certainly you're right that it's not a common request though which is a good point and slightly different than mine. However, it's all moot now because it can be done! Awesome!
An example would be the dict.fromkeys(...) method, as the output type is variable with respect to both the dict (sub-)class as well as the key type (see python/typeshed#3800). This example above can, admittedly, be more or less resolved by explicitly re-annotating aforementioned method in every single subclass.
Another less easily resolved example one would be the np.asanyarray(a, dtype=..., ...) function in numpy. Per the documentation: "Convert the input to an ndarray, but pass ndarray subclasses through.".
What this means is that, similar to dict.fromkeys(...), the output is variable with respect to both any passed ndarray subclasses (the a parameter) as well as the type of the embedded scalars (the dtype parameter).
Examples
In [1]: importnumpyasnpIn [2]: classTestArray(np.ndarray):
...: ...
...:
In [3]: array: TestArray=np.array([0, 1, 2]).view(TestArray)
In [4]: array# array of integersOut[4]: TestArray([0, 1, 2])
In [5]: np.asanyarray(array, dtype=float) # array of floatsOut[5]: TestArray([0., 1., 2.])
Reacted by Joshua Bronson, Carlos Florensa, Mike McDermott, Alex Waygood, YoungHyun Cho, Michael Scott Asato Cuthbert, Evan Huang, fabi.s, Díaz-Esparza and Alexander Schepanovski
Just ran into this limitation when trying to type methods of a generic class that return an instance of the same generic class but with other arguments. This is actually very common for classes that can easily have some properties changed, like numpy arrays or torch tensors and their device (gpu or cpu) or dtype (int, float, ...):
DeviceTransferableT = TypeVar("DeviceTransferableT", bound="DeviceTransferable")
class DeviceTransferable(Generic[DeviceT]):
def to_device(self: DeviceTransferableT, device: Type[DeviceT]) -> DeviceTransferableT[DeviceT]: # error!
... # some great implementation
class SomeOtherClass(DeviceTransferable):
"""Would prefer not to re-implement `to_device` in every inheriting class."""
@florensacc Can you expand the example so that I can actually run it by mypy? A few things are missing and I can’t guess what their definitions should be.
Here's a full example, which throws two errors: error: Type variable "DeviceTransferableT" used with arguments and Argument 1 to "f" has incompatible type "SomeOtherClass[Device0]"; expected "SomeOtherClass[Device1]".
importattrimportabcfromtypingimportType, TypeVar, Generic, ClassVarclassDevice(abc.ABC):
device_id: ClassVar[int]
classDevice0(Device):
device_id=0classDevice1(Device):
device_id=1DeviceT=TypeVar("DeviceT", bound=Device)
AnotherDeviceT=TypeVar("AnotherDeviceT", bound=Device)
DeviceTransferableT=TypeVar("DeviceTransferableT", bound="DeviceTransferable")
@attr.s()classDeviceTransferable(Generic[DeviceT]):
device: DeviceT=attr.ib()
defto_device(self: DeviceTransferableT, device: Type[AnotherDeviceT]) ->DeviceTransferableT[AnotherDeviceT]:
returnattr.evolve(self, device=device)
@attr.s()classSomeOtherClass(DeviceTransferable[DeviceT]):
"""Would prefer not to re-implement `to_device` in every inheriting class."""passC_in0=SomeOtherClass(device=Device0())
C_in1=C_in0.to_device(Device1)
deff(c: SomeOtherClass[Device1]) ->None:
print(f"The device of c is {c.device}, but its type is still SomeOtherClass[Device0]!")
f(C_in1)
without the "argument" to the TypeVar this code runs happily and prints my message: The device of c is <class '__main__.Device1'>, but its type is still SomeOtherClass[Device0]!
Reacted by Daniel Davis, Anton Agestam, Elliot Cameron, daa, io, Neil Girdhar, Joel Berkeley, Timofei Kukushkin, Michael Christensen, Alexander Hultnér and 15 more
Finally found this issue after running into TypeError: 'TypeVar' object is not subscriptable and searching around for the right way to do this. Turns out there isn't. Has there been any change in status for the implementation? The use-case might be limited for the annotations themselves, but if utilized in a couple core libraries, the end-user experience could be greatly improved by the resulting hints in IDEs.
I empathize with @tek, but unfortunately can't leave Python for the strongly-typed shores of Scala or Haskell, due to Python's ML ecosystem and relatively easy learning curve.
Reacted by Neil Girdhar, Joren Hammudoglu, Mike McDermott, Benoît Dubreuil, Dave, Sebastian Wagner, lovetoburnswhen, internetimagery, Matthieu Bizien, Tushar Chandra and 8 more
Reacted by Torsten Schmits, Mike McDermott, Matthieu Bizien, Redowan Delowar and lapraswastaken
Hm, I believe I've finally run into this. Here's my example, let me know if I'm misunderstanding something.
Let's say you're adding static types to a Mongo ODM/ORM-type framework. You define your model using a class.
@dataclassclassUser:
__collection__="users"# This is the "table", or collection in Mongo. Class variable, not instance_id: ObjectId# Every Mongo document has an ID, most often ObjectId, but can be maybe str or something elseusername: str# A random property
Now you write a function to query for this model. We write a generic protocol for models, parametrizing by the ID type:
But no, Mypy treats them as two separate type variables (in other words, I can pass in an int and it doesn't catch it).
And C cannot be changed to TypeVar("C", bound=MongoModel[ID]) either.
Reacted by Neil Girdhar, sobolevn, Benjamin D. Killeen, Joren Hammudoglu, Elliot Cameron, Fan Jiang, Mike McDermott, lovetoburnswhen, internetimagery, ZhengYu, Xu and 5 more
Just to extend the list of people encountering it, I also ran into this. My example is similar to the ones above in spirit but aggravated by extraction of the generic class at runtime, which is possible since python 3.8. I am not sure whether it is officially recommended but I really like this mechanism :). Unfortunately, using it will very often require subscriptable TypeVars, like in the sketch below.
For simplicity I slightly changed the real signature. In real use cases I load the registry from a file and not from prepared list, so polymorphism makes a bit more sense there.
This is very useful and currently not possible AFAIK.
Reacted by sobolevn, Neil Girdhar, Harald Husum, Joren Hammudoglu, James Chua, Mike McDermott, marian-code, Ramazan Elsunkaev, lapraswastaken, Janek Nouvertné and 6 more
Reacted by Harald Husum, Lorenzo, Michael Scott Asato Cuthbert, Andrew Sansom, Zac Scott, Darkavid, Díaz-Esparza, Pragy Agarwal, Alexander Schepanovski and soufianeamini
@gvanrossum makes the point that this isn't a commonly requested feature. I think that is correct to some extent, although the level of activity in this thread is making me slightly reconsider. I think it isn't commonly requested because it is a fairly advanced technique. But, I think my example, as well as many other examples in this thread are showing that fundamental library code in fairly prevalent use would be able to benefit from HKT in the sense that annotations would more accurately describe actual behavior. Often times this library code is maintained by skilled programmers that could leverage HKT if available. This is a benefit that then trickles down to the wider python ecosystem, including users that have never heard of HKT, but might leverage type checkers. You don't need to be a wizard to enjoy more accurate lib function call return types.
Reacted by Sebastian Wagner, sobolevn, areeh, Neil Girdhar, Benjamin D. Killeen, Adrian Garcia Badaracco, Dobatymo, James Chua, Alex Waygood, Andrew Beyer and 22 more
Reacted by Ian Rose, Stephen Moore, Albin Skott, Shahriar Heidrich, Díaz-Esparza and Pragy Agarwal
@harahu - That is an excellent example. I think you described the situation perfectly. I've been in the exact same spot of wanting to write a transformation over a map-like collection, and resorting to some zany nested type signatures, when really all I wanted to do was turn some D[K, V] into a F[K, M] based on some provided type F and a transform func(v: V) -> M.
HKT is a fairly sophisticated tool that many folks do not even know to reach for, but when you are in a situation which benefits it, many doors are unlocked. HKTs often greatly simplify the type signature, like in the example above. It shows up most frequently when you want to "transmute" some object (typically a container type) into a target (container) type, but you want the internal types to be preserved.
This pattern does not seem to show up frequently when writing application code. Most software engineers probably do not need to know about it if they don't want to. However, when writing library code for others to consume, the pattern shows up quite a lot. Probably because it's a fairly high level abstraction, and that kind of abstraction lends itself to reuse.
In my opinion, HKTs would push the envelop for maintainability and ergonomics for maintainers of libraries that embrace the static typing discipline. In the same vein, it would not add any cognitive overhead for those looking to implement libraries, nor those looking to start writing libraries with static types. If anything, I think it has the potential to shorten the learning curve for those with medium experience with type systems (such as myself). I have definitely gotten myself into some really convoluted type signatures trying to express the transformation I wanted. Desire to use HKTs seems to scale with familiarity with type systems and abstracting over generics.
Reacted by Sebastian Wagner, Neil Girdhar, Alex Waygood, Anthony Lobko, Harald Husum, Anton Agestam, ksonj, Carlos Ramos Carreño, Jakob Gerhard Martinussen, Lorenzo and 10 more
I think it has the potential to shorten the learning curve for those with medium experience with type systems (such as myself).
💯 When you have generics but not HKT, you end up having to twist yourself into knots trying to express what would otherwise be simple. HKT is merely generics that works everywhere instead of generics that only works in some places.
Reacted by Alex Waygood, Carlos Ramos Carreño, Neil Girdhar, Harald Husum, Scott Moore, Pyprohly, Lorenzo, Ramazan Elsunkaev, ZhengYu, Xu, lapraswastaken and 11 more
hey folks, another ORM use case coming in here, this system is in fact what we would have used for sqlalchemy2-stubs had it existed to encapsulate our structure of ColumnElement[TypeEngine[_T]], but as it's not, SQLAlchemy 2.0's typing will likely do what the original sqlalchemy-stubs did which is consider it to be ColumnElement[_T]. this solves most of the practical problems at the expense of looking a little funny and breaking the hierarchy a bit.
Reacted by Alex Waygood, Anton Agestam, Mathias Sven, James Hilton-Balfe, ZhengYu, Xu, Myles, lapraswastaken, Stanislav Zmiev, benedikt-bartscher, Darkavid and 2 more
In such cases, we recommend using an explicit type for self
in the https://peps.python.org/pep-0673/#use-in-generic-classes section, this recommendation doesn't work for the use case here. Since no specific mention of higher-kinded types is mentioned in the PEP, perhaps this use case wasn't even in mind when the PEP was written?
Note that we reject using Self with type arguments, such as Self[int]. This is because it creates ambiguity about the type of the self parameter and introduces unnecessary complexity:
Yes I would be in support of this, it was something I considered but as HKT wasn't/isn't a thing I didn't think it would be a good idea to include in the PEP
I just found out about the Self type added recently added to Python 3.11.
Could that be extended to add support for higher-kinded types?
I'm strongly against this. We'd explicitly discussed such cases during the
PEP drafting and rejected it with:
Note that we reject using Self with type arguments, such as Self[int].
This is because it creates ambiguity about the type of the self parameter
and introduces unnecessary complexity:
I agree with @pradeep90. I think this would create significant confusion and complexity. It would also deviate from the Self type in other languages.
Good to know, thank you. Do you have any ideas about how support for HKTs could be added in a way that would match other languages that already support them?
@pradeep90, please reread #548 (comment). Your playground link does not exercise the functionality this is asking for. You haven't created a subclass of BidirectionalMapping. If you do, you will see that its inherited inverse undesirably returns a BidirectionalMapping, rather than an instance of the subclass.
Do you have any ideas about how support for HKTs could be added in a way that would match other languages that already support them?
I don't consider this an example of HKT. I think of Self as a type that is implicitly specialized with the type parameters of the class that encloses it. In your example, it's effectively bound to the type BidirectionalMapping[KT, VT]. Specializing it a second time therefore doesn't make sense, any more than it would make sense to re-specialize list[int].
Reacted by Pradeep Kumar, Ashrith Sagar and soufianeamini
You haven't created a subclass of BidirectionalMapping. If you do, you will see that its inherited inverse undesirably returns a BidirectionalMapping, rather than an instance of the subclass.
Guido's thoughtful snippet demonstrates what you were trying to get at and is the kind of repro I was asking for (mypy playground, for any future readers trying to follow).
Yeah, this is one of the self cases where you'd need to add explicit stubs to the subclass methods, like you do in frozenbidict. The exact same thing would occur for the generic Container example in the PEP: if we subclassed it, we would need to explicitly specify the subclass method's return type.
If we eventually get HKTs in the type system, that would have to be the solution here rather than co-opting the Self type. As @erictraut mentioned, Self is intended to be a simple replacement for a handwritten TypeVar and helps with almost all cases except the ones in generic classes where we need to tinker with the generic type parameters (as in the PEP's Container or your BidirectionalMapping).
Reacted by Joshua Bronson, Jelle Zijlstra and Ashrith Sagar
Thanks @Gobot1234, but the SelfBidirectionalMapping[KT, VT] in that snippet (and similarly the SelfBidirectionalMapping[VT, KT]) cause error: Type variable "SelfBidirectionalMapping" used with arguments.
In any case, I don't think this is getting the job done.
Here's a more complete example based on your snippet, demonstrating that we do need HKTs (or some other not-yet-implemented feature) to make this use case work:
Thanks @Gobot1234, but the SelfBidirectionalMapping[KT, VT] in that snippet (and similarly the SelfBidirectionalMapping[VT, KT]) cause error: Type variable "SelfBidirectionalMapping" used with arguments.
In any case, I don't think this is getting the job done.
Here's a more complete example based on your snippet:
Going way back, I think there's a potential workaround for the Awaitable-or-None case, but it requires code reorganization because of the lack of higher-kinded types. Something like
# Assume Command[T] is some kind of hacky parametric newtype or container
class Redis:
def get(self, *args) -> Command[str]:
...
return Command[str](command)
def set(self, *args) -> Command[None]:
...
return Command[None](command)
def exists(self, *args) -> Command[bool]:
...
return Command[bool](command)
# ... and many MANY more ...
class ConnectionExecutor:
def execute(self, command: Command[T]) -> Coroutine[Any, Any, T]:
return self.connection.execute(...)
class PipelineExecutor:
def execute(self, command: Command[T]) -> None:
self.pipeline.append(command)
And then you can have an executor variable and a redis variable, and instead of redis.set(*args) # type: ???, you have executor.execute(redis.set(*args)), which knows what the return type is because we've got a sensible value from the Redis object's method, passed into the executor, which has a known type.
(I don't know if it makes sense to still have the Redis class in that case, or to just have a module with a bunch of top-level functions.)
(Anyway, note that this means that the wrapper methods explicitly can't wrap multiple calls or anything like that, but writing methods like that is already broken under the original code, so...)
Assuming it still makes sense for all of those classes to exist, then the HKT issue can be made to re-manifest by saying "I'd rather have the Redis and Executor classes combined into one."
I tried to sketch out what typing that would look like, and it got so messy that I'm sure I was overcomplicating it somehow, so I'm going to stop for now.
I just recently came across this issue myself and realized that there could be an extremely simple fix using intersection types assuming they would work with type variables.
Example:
fromcollectionsimportUserListfromtypingimportList, TypeVarT=TypeVar("T")
ListType=TypeVar("ListType", bound=List)
defappend(L: ListType&List[T], x: T) ->ListType&List[T]:
L.append(x)
returnLL: UserList[int] =append(UserList[int](), 0) # Should pass, as it is both UserList and List[int].
In this case, ListType & List[T] is the desired behavior for ListType[T], the HKT desired in this thread.
This would however require type intersections to support generics i.e. UserList[int] == UserList & List[int].
@SimpleArt that's neat and I can imagine that would work in some cases, but I don't believe it solves the full problem. For example, unless I'm mistaken, you can't use it in functor*
classFunctor(ABC):
defmap(self: F[A], f : Callable[[A], B]) ->F[B]:
...
because the type variable F can't be applied to type variables A or B in the way List can in your example.
*someone tell me if I've got functor wrong - i'm not clear what it looks like in python
@joelberkeley It would, I'm suggesting F[A] in your example be interpreted as F & F.bound[A], where F.bound is the bound for your type variable e.g. TypeVar("F", bound=List).
In this case it looks like F is the previously mentioned Self type. Fully written out, your example would be:
F doesn't have a bound in my code, but maybe let's wait for someone else to chime in because I'm not very confident in my definition of Functor (specifically, whether Functor should be generic in F, the "container type" if you will).
What I do believe, however, is that Functor isn't generic in T (the "element type" if you will) so you wouldn't be able to write Functor[A]. Indeed, I think it's core to the definition of Functor that it's not generic in T
@joelberkeley Perhaps we are misunderstanding each other. The self parameter must* have type Functor in your example, that's how Python methods work. That means Functor must be a subtype of F[A], which according to what you inherited that won't be the case
Perhaps what you really wanted was another separate argument for the F[A]? Or like I suggested, it is Functor which should be Generic? Or maybe you meant Functor[F[A]]?
I think what's confusing me is that I'm trying to inherit Functor in its container type, rather than make instances of it (which is the more usual approach). In that case, Functor is like
I believe that by bounding F on Kind1, you're implicitly recreating the List example. For example, you can't do this with types you don't own, which don't subclass Kind1, including stdlib types. That might be resolvable with a Protocol but you'll have other problems if you try to do that such as Protocols requiring covariant type parameters. I don't believe it's appropriate to require covariance in a general functor.
In this example, when doing x.add, self is implicitly bound as Parent[int] from the Parent class itself. It is also simultaneously bound as Child[int, str] via Self. In this way, we have two types being bound to self simultaneously.
The create example mentioned by @sobolevn is a bit more difficult to express, though, since T is not bound to anything, therefore referring to its type constructor is not exactly trivial.
I'm pretty sure it's possible to run into issues related to how __new__ is handled by type checkers, though, which might add onto the resulting complexity.
It's not clear to me how this would compose with PEP 695 if both were to be accepted. In particular, if the notion of defining TypeVars as global variables were deprecated, how would your proposal work? You will need to consider the scoping rules carefully.
It may be instructive to look at other languages that support HKTs. You'll notice that I did a survey of type parameter syntax for other languages as part of PEP 695 (see Appendix A). I think it would be useful to do a similar survey of other languages that support HKTs. To my knowledge, very few of them do. It would be interesting to understand why that's the case — and why you think this added level of complexity is justified in the Python type system when most languages have not gone this route. I've seen a few examples of where it might be useful, but they strike me as edge cases. I'd want to see evidence that such a feature would be useful to more than just a handful of users because it will not be easy to implement such a feature within the various Python type checkers.
The _ syntax is a bit more extensive than ...; for example, T[_, _] is a HKT with two generic types.
This essentially would allow an arbitrary amount of generic types in HKTs.
I was suggesting T[..., ...] for an HKT with two generic types. The character sequence ... is already a defined token in Python that generally means "this is a stand-in for something that will be supplied elsewhere". For example, consider how it's used in a Callable type annotation or a ParamSpec specialization.
By contrast, the character _ is not a dedicated token but an identifier, and it typically means "I don't care about the value of this identifier". PEP 634 did extend _ to mean a wildcard pattern, so maybe it's OK to extend its meaning for this use case as well, but ... strikes me as a closer fit in terms of established semantics.
Reacted by Neil Girdhar, Nikita Tikhonov, Shahriar Heidrich, Pragy Agarwal and Ashrith Sagar
That's an interesting problem: Scala and Haskell don't have subclassing of concrete classes. Do any languages have that and higher-kinded types? What about higher-kinded types and covariance? Maybe it's no different to interface inheritance with HKTs
BTW I don't think you'd parametrize over O[...] in Option. There would be a number of problems with that, e.g. how would you type hint it (Option[Option[Option ad infinitum], int])? You might drop O[...] in that line and use Self but then given that might you use Self in Functor? How does Self fit into all this?
also are PEP details on topic for this thread? I don't want to spam people
I think there are more examples where having this in functions is useful, so if you were going to go the effort of allowing Self to be subscripted (which in most type checkers is equivalent to a bound TypeVar) I don't see the point in not adding this.
type Unary[T, U] =Callable[[T], U]
defmap_magical[I: Iterable[T], T, U](function: Unary[T, U], iterable: I[T]) ->I[U]:
returntype(iterable)(map(function, iterable))
# NOTE: type checkers would need to verify that# type(instance)(...) gives an actual new instancearray=map_magical(str, [1, 2, 3]) # inferred `list[str]`
That's an interesting problem: Scala and Haskell don't have subclassing of concrete classes. Do any languages have that and higher-kinded types? What about higher-kinded types and covariance? Maybe it's no different to interface inheritance with HKTs
Just a note: Scala does permit subclasses of concrete classes.
That syntax looks reasonable. The approach wouldn't work with the current (prototype) implementation of PEP 695 because the compiler constructs the TypeVars for a generic class, function or type alias prior to evaluating any expressions that use those TypeVars. In your example, the upper bound expression for TypeVar I references TypeVar T, which hasn't yet been allocated. If we wanted to support this, the compiler would need to allocate all of the TypeVars prior to evaluating their upper bound expression, then bind the objects to their corresponding names, and then evaluate their bound expressions and "inject" the result into the pre-constructed TypeVar objects. A bit messy, but doable.
HKT support would definitely require its own PEP. It shouldn't be tied to PEP 695 other than to make sure that the two compose and there's a plan for how they work together if both are accepted. HKT support will require significant spec'ing, refining, socializing, and prototyping.
Reacted by Alex Waygood, Jelle Zijlstra, Nikita Tikhonov, Paweł Rubin, Elaina, Darkavid and Ashrith Sagar
I'm open to work on such a PEP, but it definitely needs lots of discussions on its specification and implementation. Honestly, 696 seems like a nice number for it, either way! :)
I'm open to work on such a PEP, but it definitely needs lots of discussions on its specification and implementation. Honestly, 696 seems like a nice number for it, either way! :)
Right, 696 is going to be typevar defaults. I may be willing to sponsor a PEP for HKT, but given the complexity of the feature I'd like to see a prototype in a major type checker first, as well as a well-specified draft PEP.
I'd be happy to help co-write or proofread a HKT PEP. It's a feature I'm quite interested in.
I agree with the other voices in this thread, however, that this would be a significant new feature with a high degree of complexity, that we'd need to take a large degree of care over.
Reacted by Joshua Bronson, Henry Schreiner, Shahriar Heidrich and OlegAlexander
Okay, I'm going to fork peps repository and add anyone willing to participate as contributors. For discussions, we can either use GitHub Discussions or, alternatively, one can contact me on Discord at nekitdev#3556 (link to my server in case my name changes).
Thank you @nekitdev, @AlexWaygood, @JelleZijlstra, @erictraut, et al., this is exciting progress! If the bidict example should make it into the PEP as one of the motivating examples and there's anything I can do to help with that, please don't hesitate to let me know. And likewise let me know if there's anything I can do to help with testing any mypy or pyright reference implementations of this against a version of bidict that makes use of the new feature.
Reacted by Nikita Tikhonov, Saul Shanabrook, Ramazan Elsunkaev, Danipulok, Jasha Sommer-Simpson, Ryan Morshead and Paweł Rubin
It looks like the work on the new PEP has gotten a little stalled?
Just to give yet another example, music21--the most used library for processing musical scores--has both a number (n) of different ways of iterating across musical scores (parts first, position first, etc.) all of which are subclasses of the main StreamIterator class and use the same iter method, and then a number (m) of different ways of filtering which objects are returned (notes only, metadata only, custom classes, etc.) almost all of which all subclasses of the same StreamFilter class and use the same filter function. As I've been working to extensively type the library, it means that I've had to go from 2 methods that work well together to min(m, n) functions to get the correct typing. (To make matters worse, there are 3 @overload versions of each method). The lack of HKT is preventing me from wanting to implement new iteration and filtering methods in the next version. It would be great to get something like the args=1 syntax working together with the PEP695 proposals. Happy to help with the writing, though most implementation details might be beyond my understanding of the corner-cases of Functor world.
Reacted by Sebastian Wagner, Harald Husum, layday and Nikita Tikhonov
The new PEP was indeed stalled for a while. I'll try to work on it more in the future, though I'm quite busy with my university atm. Thanks for providing another example, though! Will definitely consider adding it to the PEP.
Reacted by Shahriar Heidrich, Michael Scott Asato Cuthbert and Harald Husum
PEP 695 authors may need to consider implementing functionality
that would count the number of type arguments (args) based on the definition, for instance:
type
args
Iterable[T]
1
Dict[Q, T]
2
Tuple[T, U, V]
3
It is worth noting that that definitions like Dict[Q] should be rejected by type checkers because of missing arguments.
Also there should be a change in how type checkers treat generics in expressions like type(item)(...).
What is "Unary" signifying in this case? and is attrs necessary to explain the example (for those of us who don't use it, it seems to obfuscate rather than explain. -- does "classes with PEP 695" work just as well if written as:
I like this, but I can see the objection that Self already implies "Option[T]" so "Self[T]" looks like "Option[T][T]" what about considering something like this if objections are raised:
other terms could be NonGeneric[Self][U] (if the first syntax is allowed) or ChangedGeneric[Self, [U]] (for the second). I sort of feel that the process of stripping and replacing the GenericType should be explicit.
I agree about using attrs. Would much prefer keeping to the stdlib.
Can I also suggest avoiding calling classes (such as type(self)(...)) in the examples because I'm almost certain that's a very significant hole in Python's type system that could cause confusion when defining the PEP?
I'm interested in helping with the PEP but need to see how much time I have
I agree with skipping the attrs usage in favor of the standard library. I'm simply too used to the library :p
As for the type(self)(...), it's arguably the reason the PEP exists — to note that we want to be generic over type constructors.
As for the type(self)(...), it's arguably the reason the PEP exists
I don't follow. The signature of type(self)(...) is defined by the __init__ method, which can be essentially anything. The "type contructor"-ness is the application of the class as a type-level thing to types e.g. type -> type or Foo[int], rather than the application of the class as an object constructor int -> Foo or Foo(1), or indeed int -> bool -> str -> Foo and Foo(0, True, "").
The hole I'm referring to is (and I may get this example wrong first time as I'll try to use your example rather than how i'm used to presenting it)
@dataclassclassFoo(Option[T]):
bar: int
which in python typing is valid, but map but will TypeError due to an incorrect number of arguments. Where would the type checker raise an error there? My guess is it has to be at type(self)(...) because who knows what signature type(self) will have once subclassed.
I'd like to share one of my use cases for HKT which I don't think has really been considered
@dataclassclassItem[OwnerT: BaseUser]:
id: intowner: OwnerT@dataclassclassTrade[ItemT: Item, OwnerT: BaseUser]:
sending: Sequence[ItemT] # from our inventoryreceiving: Sequence[ItemT] # from their inventory
Item's are in a user's inventory and you can't send trades for items that are in another user's inventory. A common error that I see could be solved with HKT is swapping around the sending and receiving arguments and HKT could enforce at type checking time that you don't have this issue. The code would look something like
bad_trade=Trade(receiving=our_inventory.items) # error: list[Item[OurUser]] cannot be assigned to list[Item[OtherUser]]
PEP 695 authors may need to consider implementing functionality that would count the number of type arguments (args) based on the definition, for instance:
typeargs Iterable[T]1 Dict[Q, T]2 Tuple[T, U, V]3
It is worth noting that that definitions like Dict[Q] should be rejected by type checkers because of missing arguments.
Just chiming in here, I think this is a bad idea (for now) for multiple reasons.
it would increase the complexity of an already very complex PEP.
PEP 696 would make Dict[Q] a valid subscription at runtime and it'd be perfectly valid at type checking time unless you enabled some form of missing type parameter check (which is normally gated behind strict mode).
Other than that I really like the syntax and can't wait for this to be implemented, keep up the good work :)
I agree that would be useful, but I don't believe it's higher-kinded types. There are no typevars that take another typevar there. Your example is rather a limitation on how python does typevar bounds. It's similar to this SO post of mine.
What might be confusing here is that the title says "Higher-Kinded TypeVars", but the description says "aka type constructors, generic TypeVars". I don't think this is technically correct. Higher kinded types aren't the same as type constructors. Python already has type constructors e.g. list, a type -> type. A higher-kinded type is a type that abstracts over a type constructor. We'll need to be clear about this in the PEP
Ok, would it be worth having a separate PEP just for generic bounds? It looks like most of the Mypy issues that link here are actually just asking for generic bounds not HKT. This one also. I wonder how many of the 👍 on this issue are just for generic bounds.
You linked my issue, so I’d like to chime in again: I believe my issue is a different thing than this issue, as stated by @joelberkeley. I also voiced my approval of this issue before. I want to make clear that this was not mistaken, I want both to be implemented.
It's been more than 5 years since this issue was opened. What is its current state? I'm very interested, because I've faced more than one situation where HKTs were required to properly annotate the code I've written. Thank you in advance to anyone who will enlighten me.
Reacted by Paweł Rubin, Shahriar Heidrich and benedikt-bartscher
Reacted by Ismail Bello, Matt Wittmann, Michael Scott Asato Cuthbert, Zoey, Simone Baffelli, Sofian Mejjoute and Yogesh Sajanikar
It's been more than 5 years since this issue was opened. What is its current state? I'm very interested, because I've faced more than one situation where HKTs were required to properly annotate the code I've written. Thank you in advance to anyone who will enlighten me.
Ok, would it be worth having a separate PEP just for generic bounds? It looks like most of the Mypy issues that link here are actually just asking for generic bounds not HKT. This one also. I wonder how many of the 👍 on this issue are just for generic bounds.
+1 here for generic bounds. I am running into issues with a project right now that would be easily solved with them.
Reacted by Matthew Hoffman, Riccardo Bergamaschi, Jacek Olczyk, Shahriar Heidrich, Raphael David François, Roman Valov and soufianeamini
I just wanted to say that until this is fixed, it would be impossible to type any library that makes use of the array API standard and has functions that are generic not only on the array type, but also on the dtype or shape parameters. E.g.:
What I try to say is that this is not a marginal use case, and will likely become more ubiquitous as more array-related libraries start following the standard.
Reacted by rsayn, Winston H., Riccardo Bergamaschi, Matthew Hoffman, Jack McIvor, Randolf Scholz, Serhii Tereshchenko, Simone Baffelli, Dan Kuester, Ramazan Elsunkaev and 8 more
I don't know if everyone saw, but generic TypeVar bounds have a(nother?) dedicated issue at #1226 and have also been implemented in Mypy fork basedmypy. Maybe some of the people who only need generic TypeVar bounds and not the full power of HKTs¹ want to get involved there.
¹ Roughly, as far as I understand it: When you only want to annotate some things as T[U] while referring to U by itself elsewhere in the same signature, but don't need to have a different parametrization T[W] in it as well (T, U and W all being TypeVars and T having a generic bound).
Reacted by Serhii Tereshchenko, benedikt-bartscher, anderium and Ashrith Sagar
According to PEP-695, which proposed the new (3.12) generics API:
The specified constrained types must be concrete. An attempt to use a generic type should be flagged as an error by a type checker. This is consistent with the existing rules enforced by type checkers for a TypeVar constructor call.
Any idea why? Generic bound types seem absolutely logical (I even assumed it was possible before trying it and getting disappointed).
The reason it isn't supported yet might be relevant here.
PEP 695 was intended to provide a new syntax for existing mainstream typing features. The PEP was already quite large and involved (with nearly a year of planning that went into it). It didn't make sense to introduce new orthogonal features as part of the PEP.
Almost all modern languages support generics, but very few support higher-kinded types. HKTs are an advanced (and somewhat esoteric) capability that can be useful in certain situations, but they add significant complexity to the type system.
Almost all modern languages support generics, but very few support higher-kinded types.
Correct me if I'm wrong, but the example by @rianhunter should be easily achievable in most languages that support generics.
Is it a simple, specific case of HKT? Because according to references to this issue from other issues, that's most of the demand.
@omer54463 I don't know what would be specific about it, but it's also hard to tell what the expectations are. If the expectation is for the return type of Foo.bar to somehow vary over A, it's not clear to me with the syntax given how it's expected to vary. In my opinion it's an incomplete example because it doesn't show what the expectations are on usage of these classes.
Perhaps it would be useful to instead provide an actual working piece of C++ to show the capability you mean to compare with?
The call to non_interface_method works because we don't just have T = int - we have I = Counter<int> too.
This way, the member's type, Counter<int>, is fully known.
This isn't possible in python:
container.get() returns Interface[int], and not the specific type that implements Interface[int], so we don't have any methods except for the interface.
It should be possible if we allow something like this:
In addition, even Typevar("I", bound=Interface[int]) is unallowed!
This is a very weird restriction - there is nothing generic about Interface[int] - all of the parameters are resolved.
Like @sobolevn mentioned almost 4 years ago, the closest you can get to a solution, is by using the approach that's used in python-returns:
It would work. It's also pretty ugly and counterintuitive. I really don't want to introduce returns into an existing company's codebase out of fear of the termination of my employment.
I'm suggesting making the syntax I mentioned a supported feature - is this not an appropriate place for that?
@omer54463 Perhaps I wasn't clear about this, but I meant to show that the currently best "solution" to HKT will probably require significant changes to your codebase, requires you to use mypy and a third-party mypy-plugin, and results in verbose and difficult to read code.
The syntax you mentioned earlier uses typing.TypeVar, instead of the new PEP 695 syntax.
I guess that the desireable HKT syntax would look like either
This makes no sense to me.
T can be any type, not all types receive type arguments.
I am not saying this could not be checked and supported, but it's a much bigger feature.
@omer54463 I don't know what you mean with the first example. It makes me suspect that you don't understand PEP 695, so let me "backport" my previous example into the pre- PEP 695 era:
This example made me realise that the "explicit notation" from my earlier post isn't going to work.
I can think of two "explicit notations":
map_async[R, T: Awaitable[V]](fn: Maps1[V, R], aval: T[V]): T[R] - V occurs in both fn and aval, but is bound to V@Awaitable
map_async[V, R, T: Awaitable[V]](fn: Maps1[V, R], aval: T[V]): T[R] - V is bound to V@Awaitable and to itself, implying equivalence of T[V] and T[R], but V and R need not be equivalent
Both cases are self-contractictory, making my earlier proposal for an "explicit notation" a bad idea.
This leaves the "implicit notation", i.e. which I used in this post's example (i.e. map_async[V, R, T: Awaitable](fn: Maps1[V, R], aval: T[V]) -> T[R]).
As far as I can tell, it doesn't have any of the problems that the explicit variant has.
Additionally, it has the benefit that there is only one obvious to do it (and coincidentally, I'm dutch).
Reacted by Omer Lubin, Kyle Smith and Ashrith Sagar
@omer54463 Your 2nd and 3rd signature specifications with 4 type parameters are invalid.
To see why, consider the map_async(str, asyncio.Task[int]) example:
Maps1[V, R] matches str: Any -> str
V binds to Any
R binds to str
AV[V] binds to asyncio.Task[V]
V binds to int, because and int <: Any
AR[R] is narrowed to AR[str]
AR cannout be bound
So a type checker has no way of inferring the type of AR, unless you consider Awaitable to be its default, in which case it will always return an Awaitable[R], which defeats the purpose of HKT in this example.
The important points about the status of this feature and how to contribute have scrolled out of GitHub's very limited window of comments visible on page load by now, which makes them hard for newcomers to this issue to find. So here is a summary again:
@JelleZijlstrahas indicated interest in sponsoring a PEP, conditional on a prototype implementation in a major type checker and a well-specified draft PEP.
Drafting the PEP takes place in @nekitdev's fork of the peps repo. The stub PEP draft so far contains a few examples of the proposed syntax.
That same repo's GitHub Discussions forum forum has been designated as the place to discuss the PEP (and presumably the prototype implementation?). Some limited further discussions have taken place there.
If you want to be notified of new discussion threads, I think you have to set the whole repo as "watched" in GitHub?
Maybe @tek or someone else who is allowed to edit it can put this in the original issue so new arrivals find it quickly?
Reacted by Maximilian Roos and Finite State Machine
Reacted by Michael Scott Asato Cuthbert, lena, Brandon Papworth and Ellis Breen
Drafting the PEP takes place in @nekitdev's fork of the peps repo. The stub PEP draft so far contains a few examples of the proposed syntax.
I wanted to post this on the linked PEP draft, but I'm getting a 404. Did it maybe move? (cc: @nekitdev)
At any rate, I'm pretty sure I have another use case. I'm currently writing an asset management system that supports the idea of presentation variants, and each presentation variant has a metadata dict. However, the exact format of the metadata dict is determined by the kind of one of the arguments to its constructor. More specifically, I've implemented multiple asset builder classes, which all inherit from a base Builder, and may then themselves be subclassed further. Each of the Builder subclasses define their own required metadata keys, but the PresentationVariantInfo is generic with respect to the actual Builder subclass. Simplifying a lot, it looks something like this:
Where the type of the metadata can reference the kind of the nested generic B.
Unnecessary details:
Actually, in my case, I actually need both HKT and an intersection type (cross ref here, though this is a different example I didn't include there). In reality, the AMS supports both a build step (always static for any given asset version, for example, reading a template file into memory) and a render step (actually rendering out the asset, for example, rendering a jinja template). Because both the builder and the renderer specify metadata keys, I really need the intersection of both of their typed dicts. So in that case it would look something like this:
classBuilder[MD]:
...
classRenderer[MD]:
...
classPresentationVariantInfo[B: Builder, R: Renderer]:
builder: Brenderer: R# The intersection of the MD type argument for both the builder and the# renderermetadata: B.MD&R.MD
Activity
gvanrossum commentedon Apr 26, 2020
Honestly I have no idea what higher-kinded type vars are -- my eyes glaze over when I hear that kind of talk. :-)
I have one more suggestion, then you're on your own. Use a common base class that has an
Optional[Coroutine[...]]return type and derive both the regular Redis class and the RedisPipeline class from it.samuelcolvin commentedon Apr 26, 2020
Okay, so the simple answer is that what I'm trying to do isn't possible with python types right now.
Thanks for helping - at least I can stop my search.
gvanrossum commentedon Apr 26, 2020
samuelcolvin commentedon Apr 26, 2020
humm, but the example above under "Basic example" I would argue IS type-safe.
All the methods which end
return self.execute(...)return whatexecutereturns - either aCoroutineorNone.Thus I don't see how this as any more "unsafe" than normal use of generics.
jab commentedon Apr 29, 2020
@gvanrossum, I can relate!
I wonder if bidict provides a practical example of how this issue prevents expressing a type that you can actually imagine yourself needing.
What we want is for mypy to know this:
merely from a type hint that we could add to a super class. It would parameterize not just the key type and the value type, but also the self type. In other words, something like:
where
$SELF_TYPEwould of course use some actually legal syntax that allowed composing the self type with the other parameterized types.gvanrossum commentedon Apr 30, 2020
Okay, I think that example is helpful. I recreated it somewhat simpler (skipping the inheritance from
Mappingand the property decorators):This passes but IIUC you want the ABC to have a more powerful type. I guess here we might want to write it as
Have I got that?
jab commentedon Apr 30, 2020
Exactly! It should be possible to e.g. subclass bidict (without overriding inverse), and have mypy realize that calling inverse on the subclass gives an instance of the subclass (with the key and value types swapped as well).
This isn’t only hypothetically useful, it’d really be useful in practice for the various subclasses in the bidict library where this actually happens (frozenbidict, OrderedBidict, etc.).
Glad this example was helpful! Please let me know if there’s anything further I can do to help here, and (can’t help myself) thanks for creating Python, it’s such a joy to use.
gvanrossum commentedon Apr 30, 2020
Ah, so the
@abstractmethodis also a red herring.And now I finally get the connection with the comment that started this issue.
But I still don't get the connection with @samuelcolvin's RedisPipeline class. :-(
sobolevn commentedon Apr 30, 2020
I would also say that this example is really simple, common, but not supported:
We use quite a lot of similar constructs in
dry-python/returns.As a workaround I am trying to build a plugin with emulated HKT, just like in some other languages where support of it is limited. Like:
bow: https://bow-swift.io/docs/fp-concepts/higher-kinded-types/fp-ts: https://github.com/gcanti/fp-ts/blob/master/src/HKT.tsPaper on "Lightweight higher-kinded polymorphism": https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf
TLDR: So, instead of writing
T[K]we can emulate this by usingHKT[T, K]whereHKTis a basic generic instance processed by a custommypyplugin. I am working on this plugin for already some time now, but there's still nothing to show. You can track the progress here: https://pypi.org/project/kinds/ (part of dry-python libraries)samuelcolvin commentedon Apr 30, 2020
Sorry if I wasn't clear. I'll try again to explain:
I have a class with many (~200) methods, they all return coroutines with different result types (
None,bytes,str,intorfloat). I want a subclass with the same internal logic but where all those methods returnNone- I can do this in python, but not with type hints currently (here's the actual code if it helps)So roughly I want:
Except I don't want to have to redefine all the methods on
Bar. Assuming I could create my ownAlwaysNonetype which even when parameterised told mypy the result would always beNoneI think the feature requested here could solve my problem.
In other words if mypy could understand
I'd be in the money, but quite understandably it can't.
I've currently got a working solution where i generate a
.pyistub file with definitions for all these methods but changing the return type toNone(see here) so I'm not in immediate need of this anymore.Kentzo commentedon Apr 30, 2020
It looks like you want completely different signatures whose likeness is limited to their names and arguments:
It's understandable you're looking to reuse some common "template", but I fail to see what it has to do with higher-kinded types.
samuelcolvin commentedon May 6, 2020
Hi @Kentzo, that's incorrect, these are not completely different signatures.
Please review the code here; as you can see these are functions which may return an awaitable object, not coroutines. This approach and similar, while not necessarily obvious to beginners, is relatively common in libraries which make extensive use of asyncio and the standard library asyncio code.
If you're not sure how it works, feel free to submit an issue on that project and I'll endeavour to explain it to you.
Kentzo commentedon May 6, 2020
In your initial example:
These are different signatures. Functions annotated to accept
Redisas an argument would expectexecuteto return a coroutine.samuelcolvin commentedon May 6, 2020
This is off-topic, please create an issue on async-redis if you want to discuss this more.
In short, yes they're different signatures, but I think my explanation above gives enough detail on what I'm doing and why I think it relates to this issue.
3noch commentedon Aug 28, 2020
+1 on this. It's hard to say the use cases are rare when it's not even possible to express... There may be countless use cases hiding behind duck typing that simply can't be checked by the type system and therefore certainly aren't recognized as such. Having a type safe generic "Mappable" class as shown in the OP would be really useful.
gvanrossum commentedon Aug 28, 2020
That makes no sense. You can express the OP's example just fine in untyped Python, you just can't express the types correctly. So if there were "countless" real examples we would encounter frequent user requests for type system extensions to allow typing this correctly. I haven't seen a lot of those.
Do you have an actual (non-toy) example?
sobolevn commentedon Aug 28, 2020
You actually can have Higher Kinded Types in Python with full
mypysupport.Here's how
Mappablelooks like from OP:MappableakaFunctor: https://github.com/dry-python/returns/blob/master/returns/interfaces/mappable.py3noch commentedon Aug 28, 2020
@sobolevn That's awesome! Thank you!
@gvanrossum Inexpressible in the type system is what I was referring to. My point was that writing this off as rare seems premature since Python's types system is not a hard stop on your ability to keep going. So it might be actually quite common and how would we know? Certainly you're right that it's not a common request though which is a good point and slightly different than mine. However, it's all moot now because it can be done! Awesome!
BvB93 commentedon Aug 29, 2020
An example would be the
dict.fromkeys(...)method, as the output type is variable with respect to both thedict(sub-)class as well as the key type (see python/typeshed#3800). This example above can, admittedly, be more or less resolved by explicitly re-annotating aforementioned method in every single subclass.Another less easily resolved example one would be the
np.asanyarray(a, dtype=..., ...)function in numpy. Per the documentation: "Convert the input to an ndarray, but pass ndarray subclasses through.".What this means is that, similar to
dict.fromkeys(...), the output is variable with respect to both any passedndarraysubclasses (theaparameter) as well as the type of the embedded scalars (thedtypeparameter).Examples
gvanrossum commentedon Aug 29, 2020
NeilGirdhar commentedon Sep 16, 2020
Ran into this today:
I could have worked around it with an intersection operator:
Would intersection help in the general case without requiring as much effort?
florensacc commentedon Sep 30, 2020
Just ran into this limitation when trying to type methods of a generic class that return an instance of the same generic class but with other arguments. This is actually very common for classes that can easily have some properties changed, like numpy arrays or torch tensors and their
device(gpu or cpu) ordtype(int,float, ...):Is there any other solution?
gvanrossum commentedon Sep 30, 2020
@florensacc Can you expand the example so that I can actually run it by mypy? A few things are missing and I can’t guess what their definitions should be.
florensacc commentedon Sep 30, 2020
Here's a full example, which throws two errors:
error: Type variable "DeviceTransferableT" used with argumentsandArgument 1 to "f" has incompatible type "SomeOtherClass[Device0]"; expected "SomeOtherClass[Device1]".gvanrossum commentedon Sep 30, 2020
Oh, @attr.s :-(
Can you do it without?
florensacc commentedon Sep 30, 2020
sure, you can remove all attr.s stuff and use this class definition:
without the "argument" to the
TypeVarthis code runs happily and prints my message:The device of c is <class '__main__.Device1'>, but its type is still SomeOtherClass[Device0]!gvanrossum commentedon Sep 30, 2020
Okay, I get it. If/when we fix this we'll make sure to have a test like that. Thanks!
sobolevn commentedon Oct 30, 2020
We have released a first prototype of Higher Kinded Types emulation for Python.
It is available for everyone to try! Quick demo: https://gist.github.com/sobolevn/7f8ffd885aec70e55dd47928a1fb3e61
Here's how a function's signature will look like:
Source code: https://github.com/dry-python/returns
Docs: https://returns.readthedocs.io/en/latest/pages/hkt.html and https://sobolevn.me/2020/10/higher-kinded-types-in-python
sebastian-philipp commentedon Jan 18, 2021
See ceph/ceph#38953 for a real world use case
mgr/orchestrator: ignore Liskov substitution principle violation
mgr/orchestrator: ignore Liskov substitution principle violation
benjamindkilleen commentedon Apr 1, 2021
Finally found this issue after running into
TypeError: 'TypeVar' object is not subscriptableand searching around for the right way to do this. Turns out there isn't. Has there been any change in status for the implementation? The use-case might be limited for the annotations themselves, but if utilized in a couple core libraries, the end-user experience could be greatly improved by the resulting hints in IDEs.I empathize with @tek, but unfortunately can't leave Python for the strongly-typed shores of Scala or Haskell, due to Python's ML ecosystem and relatively easy learning curve.
Tinche commentedon May 19, 2021
Hm, I believe I've finally run into this. Here's my example, let me know if I'm misunderstanding something.
Let's say you're adding static types to a Mongo ODM/ORM-type framework. You define your model using a class.
Now you write a function to query for this model. We write a generic protocol for models, parametrizing by the ID type:
Our
Userclass is thus aMongoModel. Now the actual querying function. We can query just passing in the ID.There's nothing to put instead of
???, is there?My first instinct was to try:
But no dice, obviously.
Second approach: I hoped Mypy would realize
MongoModelalready referencesID(since it's defined with it), so I can write this:But no, Mypy treats them as two separate type variables (in other words, I can pass in an int and it doesn't catch it).
And
Ccannot be changed toTypeVar("C", bound=MongoModel[ID])either.MischaPanch commentedon Aug 17, 2021
Just to extend the list of people encountering it, I also ran into this. My example is similar to the ones above in spirit but aggravated by extraction of the generic class at runtime, which is possible since python 3.8. I am not sure whether it is officially recommended but I really like this mechanism :). Unfortunately, using it will very often require subscriptable TypeVars, like in the sketch below.
For simplicity I slightly changed the real signature. In real use cases I load the registry from a file and not from prepared list, so polymorphism makes a bit more sense there.
This way I can avoid code duplication but typing
TSettingsRegistry[TSettings]is currently disallowed, of course.vnmabus commentedon Sep 14, 2021
Just another use for this: use the new
TypeGuardto check the types of a collection:This is very useful and currently not possible AFAIK.
harahu commentedon Nov 16, 2021
Another potential use case example for HKTs:
https://toolz.readthedocs.io/en/latest/_modules/toolz/dicttoolz.html#valmap
I've made my own typed version of it, like so:
but in order to preserve type information, I would much rather want to type it as:
@gvanrossum makes the point that this isn't a commonly requested feature. I think that is correct to some extent, although the level of activity in this thread is making me slightly reconsider. I think it isn't commonly requested because it is a fairly advanced technique. But, I think my example, as well as many other examples in this thread are showing that fundamental library code in fairly prevalent use would be able to benefit from HKT in the sense that annotations would more accurately describe actual behavior. Often times this library code is maintained by skilled programmers that could leverage HKT if available. This is a benefit that then trickles down to the wider python ecosystem, including users that have never heard of HKT, but might leverage type checkers. You don't need to be a wizard to enjoy more accurate lib function call return types.
xkortex commentedon Dec 6, 2021
@harahu - That is an excellent example. I think you described the situation perfectly. I've been in the exact same spot of wanting to write a transformation over a map-like collection, and resorting to some zany nested type signatures, when really all I wanted to do was turn some
D[K, V]into aF[K, M]based on some provided typeFand a transformfunc(v: V) -> M.HKT is a fairly sophisticated tool that many folks do not even know to reach for, but when you are in a situation which benefits it, many doors are unlocked. HKTs often greatly simplify the type signature, like in the example above. It shows up most frequently when you want to "transmute" some object (typically a container type) into a target (container) type, but you want the internal types to be preserved.
This pattern does not seem to show up frequently when writing application code. Most software engineers probably do not need to know about it if they don't want to. However, when writing library code for others to consume, the pattern shows up quite a lot. Probably because it's a fairly high level abstraction, and that kind of abstraction lends itself to reuse.
In my opinion, HKTs would push the envelop for maintainability and ergonomics for maintainers of libraries that embrace the static typing discipline. In the same vein, it would not add any cognitive overhead for those looking to implement libraries, nor those looking to start writing libraries with static types. If anything, I think it has the potential to shorten the learning curve for those with medium experience with type systems (such as myself). I have definitely gotten myself into some really convoluted type signatures trying to express the transformation I wanted. Desire to use HKTs seems to scale with familiarity with type systems and abstracting over generics.
3noch commentedon Dec 14, 2021
💯 When you have generics but not HKT, you end up having to twist yourself into knots trying to express what would otherwise be simple. HKT is merely generics that works everywhere instead of generics that only works in some places.
Tishka17 commentedon Dec 23, 2021
I am having two similar protocols:
Then only difference is that in second case there are async functions. I would be glad to unite them using Generics. But of cause this does not work:
zzzeek commentedon Jan 1, 2022
hey folks, another ORM use case coming in here, this system is in fact what we would have used for sqlalchemy2-stubs had it existed to encapsulate our structure of ColumnElement[TypeEngine[_T]], but as it's not, SQLAlchemy 2.0's typing will likely do what the original sqlalchemy-stubs did which is consider it to be ColumnElement[_T]. this solves most of the practical problems at the expense of looking a little funny and breaking the hierarchy a bit.
references for me at:
#999
sqlalchemy/sqlalchemy#7519
jab commentedon Mar 15, 2022
I just found out about the
Selftype that was recently added to Python 3.11. Could that be extended to add support for higher-kinded types?cc @JelleZijlstra @pradeep90 @Gobot1234 et al.
mwchase commentedon Mar 15, 2022
Using parameters on Self is explicitly disallowed by the PEP, so that seems like a non-starter unless the justifications can be addressed.
jab commentedon Mar 15, 2022
Thanks. If you're talking about the
in the https://peps.python.org/pep-0673/#use-in-generic-classes section, this recommendation doesn't work for the use case here. Since no specific mention of higher-kinded types is mentioned in the PEP, perhaps this use case wasn't even in mind when the PEP was written?
mwchase commentedon Mar 15, 2022
I meant
Gobot1234 commentedon Mar 15, 2022
Yes I would be in support of this, it was something I considered but as HKT wasn't/isn't a thing I didn't think it would be a good idea to include in the PEP
pradeep90 commentedon Mar 15, 2022
erictraut commentedon Mar 15, 2022
I agree with @pradeep90. I think this would create significant confusion and complexity. It would also deviate from the
Selftype in other languages.jab commentedon Mar 15, 2022
@pradeep90 writes:
I think there might be a misunderstanding? I am already doing what you proposed, and it does not help with this issue, as I commented here previously.
jab commentedon Mar 15, 2022
@erictraut writes:
Good to know, thank you. Do you have any ideas about how support for HKTs could be added in a way that would match other languages that already support them?
pradeep90 commentedon Mar 15, 2022
I hadn't read your earlier comment in the thread because I mistakenly assumed this was a fresh (typing-sig) thread.
The snippet I shared works in both Pyre and Mypy (playground link). The same goes for your existing code, where the
selfis not annotated.If you're still having trouble, could you repro your case in a Mypy playground snippet?
jab commentedon Mar 15, 2022
@pradeep90, please reread #548 (comment). Your playground link does not exercise the functionality this is asking for. You haven't created a subclass of BidirectionalMapping. If you do, you will see that its inherited
inverseundesirably returns a BidirectionalMapping, rather than an instance of the subclass.erictraut commentedon Mar 15, 2022
I don't consider this an example of HKT. I think of
Selfas a type that is implicitly specialized with the type parameters of the class that encloses it. In your example, it's effectively bound to the typeBidirectionalMapping[KT, VT]. Specializing it a second time therefore doesn't make sense, any more than it would make sense to re-specializelist[int].jab commentedon Mar 15, 2022
What would you consider this, then, and should I open a separate issue for that once I know what to call it?
pradeep90 commentedon Mar 15, 2022
Guido's thoughtful snippet demonstrates what you were trying to get at and is the kind of repro I was asking for (mypy playground, for any future readers trying to follow).
Yeah, this is one of the
selfcases where you'd need to add explicit stubs to the subclass methods, like you do infrozenbidict. The exact same thing would occur for the genericContainerexample in the PEP: if we subclassed it, we would need to explicitly specify the subclass method's return type.If we eventually get HKTs in the type system, that would have to be the solution here rather than co-opting the
Selftype. As @erictraut mentioned,Selfis intended to be a simple replacement for a handwrittenTypeVarand helps with almost all cases except the ones in generic classes where we need to tinker with the generic type parameters (as in the PEP'sContaineror yourBidirectionalMapping).Gobot1234 commentedon Mar 15, 2022
Maybe I'm missing something but if Self is meant to act like a shorthand for a bound TypeVar so surely the example which could be written as:
Could be written using Self as shown in #548 (comment)?
jab commentedon Mar 16, 2022
Thanks @Gobot1234, but the
SelfBidirectionalMapping[KT, VT]in that snippet (and similarly theSelfBidirectionalMapping[VT, KT]) causeerror: Type variable "SelfBidirectionalMapping" used with arguments.In any case, I don't think this is getting the job done.
Here's a more complete example based on your snippet, demonstrating that we do need HKTs (or some other not-yet-implemented feature) to make this use case work:
mypy-play link here: https://mypy-play.net/?mypy=latest&python=3.7&gist=5272ebc066abb4b6b32df0a336c78b00&flags=strict
Gobot1234 commentedon Mar 16, 2022
This is meant to use HKT is it not? The error (currently) is to be expected
mwchase commentedon Mar 17, 2022
Going way back, I think there's a potential workaround for the Awaitable-or-None case, but it requires code reorganization because of the lack of higher-kinded types. Something like
And then you can have an
executorvariable and aredisvariable, and instead ofredis.set(*args) # type: ???, you haveexecutor.execute(redis.set(*args)), which knows what the return type is because we've got a sensible value from theRedisobject's method, passed into theexecutor, which has a known type.(I don't know if it makes sense to still have the
Redisclass in that case, or to just have a module with a bunch of top-level functions.)(Anyway, note that this means that the wrapper methods explicitly can't wrap multiple calls or anything like that, but writing methods like that is already broken under the original code, so...)
Assuming it still makes sense for all of those classes to exist, then the HKT issue can be made to re-manifest by saying "I'd rather have the
RedisandExecutorclasses combined into one."I tried to sketch out what typing that would look like, and it got so messy that I'm sure I was overcomplicating it somehow, so I'm going to stop for now.
SimpleArt commentedon Mar 26, 2022
I just recently came across this issue myself and realized that there could be an extremely simple fix using intersection types assuming they would work with type variables.
Example:
In this case,
ListType & List[T]is the desired behavior forListType[T], the HKT desired in this thread.This would however require type intersections to support generics i.e.
UserList[int] == UserList & List[int].joelberkeley commentedon Mar 26, 2022
@SimpleArt that's neat and I can imagine that would work in some cases, but I don't believe it solves the full problem. For example, unless I'm mistaken, you can't use it in functor*
because the type variable
Fcan't be applied to type variablesAorBin the wayListcan in your example.*someone tell me if I've got functor wrong - i'm not clear what it looks like in python
SimpleArt commentedon Mar 26, 2022
@joelberkeley It would, I'm suggesting
F[A]in your example be interpreted asF & F.bound[A], whereF.boundis the bound for your type variable e.g.TypeVar("F", bound=List).In this case it looks like
Fis the previously mentionedSelftype. Fully written out, your example would be:SelfensuresfunctorB = functorA.map(f)makesfunctorBretains the same subclass asfunctorA.Functor[A] -> Functor[B]ensuresfunctorB = functorA.map(f)makesfunctorBretains the correct type variable instead of justAny.joelberkeley commentedon Mar 26, 2022
Fdoesn't have a bound in my code, but maybe let's wait for someone else to chime in because I'm not very confident in my definition ofFunctor(specifically, whetherFunctorshould be generic inF, the "container type" if you will).What I do believe, however, is that
Functorisn't generic inT(the "element type" if you will) so you wouldn't be able to writeFunctor[A]. Indeed, I think it's core to the definition ofFunctorthat it's not generic inTSimpleArt commentedon Mar 26, 2022
@joelberkeley Perhaps we are misunderstanding each other. The
selfparameter must* have typeFunctorin your example, that's how Python methods work. That meansFunctormust be a subtype ofF[A], which according to what you inherited that won't be the casePerhaps what you really wanted was another separate argument for the
F[A]? Or like I suggested, it isFunctorwhich should beGeneric? Or maybe you meantFunctor[F[A]]?joelberkeley commentedon Mar 26, 2022
I think what's confusing me is that I'm trying to inherit Functor in its container type, rather than make instances of it (which is the more usual approach). In that case,
Functoris likeHopefully that clears up the confusion.
SimpleArt commentedon Mar 26, 2022
@joelberkeley Then this is what it would look like with my suggestion:
This ensures the following types should be preserved:
joelberkeley commentedon Mar 26, 2022
I believe that by bounding
FonKind1, you're implicitly recreating theListexample. For example, you can't do this with types you don't own, which don't subclassKind1, including stdlib types. That might be resolvable with aProtocolbut you'll have other problems if you try to do that such asProtocols requiring covariant type parameters. I don't believe it's appropriate to require covariance in a general functor.SimpleArt commentedon May 19, 2022
Interestingly, some linters such as
mypyalready treatselfas an intersection of types. Here's a minimal example:In this example, when doing
x.add,selfis implicitly bound asParent[int]from theParentclass itself. It is also simultaneously bound asChild[int, str]viaSelf. In this way, we have two types being bound toselfsimultaneously.nekitdev commentedon Jul 23, 2022
I'm currently writing a PEP to propose HKTs with the
argsargument to theTypeVar. Any suggestions are welcomed!Here's how a functor would look like:
nekitdev commentedon Jul 23, 2022
Following the
bidictexample, one could do:nekitdev commentedon Jul 23, 2022
The
createexample mentioned by @sobolevn is a bit more difficult to express, though, sinceTis not bound to anything, therefore referring to its type constructor is not exactly trivial.nekitdev commentedon Jul 23, 2022
In general, any code that needs to be generic over types and type constructors needs HKTs in one way or another.
As the simplest example, the wrapping type:
nekitdev commentedon Jul 23, 2022
I'm pretty sure it's possible to run into issues related to how
__new__is handled by type checkers, though, which might add onto the resulting complexity.erictraut commentedon Jul 23, 2022
It's not clear to me how this would compose with PEP 695 if both were to be accepted. In particular, if the notion of defining TypeVars as global variables were deprecated, how would your proposal work? You will need to consider the scoping rules carefully.
It may be instructive to look at other languages that support HKTs. You'll notice that I did a survey of type parameter syntax for other languages as part of PEP 695 (see Appendix A). I think it would be useful to do a similar survey of other languages that support HKTs. To my knowledge, very few of them do. It would be interesting to understand why that's the case — and why you think this added level of complexity is justified in the Python type system when most languages have not gone this route. I've seen a few examples of where it might be useful, but they strike me as edge cases. I'd want to see evidence that such a feature would be useful to more than just a handful of users because it will not be easy to implement such a feature within the various Python type checkers.
nekitdev commentedon Jul 23, 2022
Indeed, PEP 695 does make this feature much more complex to implement.
joelberkeley commentedon Jul 23, 2022
@nekitdev given the similarity with scala's syntax in that PEP, you may find scala's syntax for higher-kinded types useful
nekitdev commentedon Jul 23, 2022
IMO Scala's HKT syntax fits well with PEP 695, though, as Eric has mentioned, HKTs are rather complex to implement type-checker-wise.
nekitdev commentedon Jul 23, 2022
Then the monad would look something like this:
@erictraut
EDIT: renamed to
Functor[T]erictraut commentedon Jul 23, 2022
That syntax approach looks good to me. A variation that may be worth considering is to use
...rather than_.nekitdev commentedon Jul 23, 2022
The
_syntax is a bit more extensive than...; for example,T[_, _]is a HKT with two generic types.This essentially would allow an arbitrary amount of generic types in HKTs.
nekitdev commentedon Jul 23, 2022
I'm not fully sure how implementing the aforementioned
Functorwould look like. Open for discussion here, I guess.erictraut commentedon Jul 23, 2022
I was suggesting
T[..., ...]for an HKT with two generic types. The character sequence...is already a defined token in Python that generally means "this is a stand-in for something that will be supplied elsewhere". For example, consider how it's used in aCallabletype annotation or aParamSpecspecialization.By contrast, the character
_is not a dedicated token but an identifier, and it typically means "I don't care about the value of this identifier". PEP 634 did extend_to mean a wildcard pattern, so maybe it's OK to extend its meaning for this use case as well, but...strikes me as a closer fit in terms of established semantics.nekitdev commentedon Jul 23, 2022
Alright, I agree that
...might be nicer (and cuter, for that matter). Back to implementingFunctor, though:I feel like this should make sense here.
joelberkeley commentedon Jul 23, 2022
That's an interesting problem: Scala and Haskell don't have subclassing of concrete classes. Do any languages have that and higher-kinded types? What about higher-kinded types and covariance? Maybe it's no different to interface inheritance with HKTs
BTW I don't think you'd parametrize over
O[...]inOption. There would be a number of problems with that, e.g. how would you type hint it (Option[Option[Option ad infinitum], int])? You might dropO[...]in that line and useSelfbut then given that might you useSelfinFunctor? How doesSelffit into all this?also are PEP details on topic for this thread? I don't want to spam people
nekitdev commentedon Jul 23, 2022
On that idea, we could throw things away all together and allow parametrizing
Selfinstead.nekitdev commentedon Jul 23, 2022
For instance, the Functor would be as follows:
Gobot1234 commentedon Jul 23, 2022
I think being able to parameterise Self only solves part of the problem, it would still leave functions like all untypeable
nekitdev commentedon Jul 23, 2022
Could you by any chance give an example?
joelberkeley commentedon Jul 23, 2022
Anywhere
Selfisn't defined, like a free functionGobot1234 commentedon Jul 23, 2022
So I was thinking this would be useful to rewrite all to look like
I think there are more examples where having this in functions is useful, so if you were going to go the effort of allowing Self to be subscripted (which in most type checkers is equivalent to a bound TypeVar) I don't see the point in not adding this.
nekitdev commentedon Jul 23, 2022
Hm, indeed, if we want to bring the full power of HKTs, we can't limit it so these examples won't work.
nekitdev commentedon Jul 23, 2022
The ultimate solution I can think of is parametrizing bounded
TypeVars, even though it doesn't exactly go nicely with PEP 695's attitude.nekitdev commentedon Jul 24, 2022
So the syntax would be something like
@erictraut what do you think?
johnynek commentedon Jul 24, 2022
Just a note: Scala does permit subclasses of concrete classes.
erictraut commentedon Jul 24, 2022
That syntax looks reasonable. The approach wouldn't work with the current (prototype) implementation of PEP 695 because the compiler constructs the TypeVars for a generic class, function or type alias prior to evaluating any expressions that use those TypeVars. In your example, the upper bound expression for TypeVar
Ireferences TypeVarT, which hasn't yet been allocated. If we wanted to support this, the compiler would need to allocate all of the TypeVars prior to evaluating their upper bound expression, then bind the objects to their corresponding names, and then evaluate their bound expressions and "inject" the result into the pre-constructed TypeVar objects. A bit messy, but doable.nekitdev commentedon Jul 24, 2022
I see, if we want to keep the allocation simpler, perhaps the following order could be required:
(still, this will probably introduce some additional things needed for the compiler to do)
Allocating all of them is also an option, which imo is a bit closer to several-step compilers, in some sense.
By the way, would this feature need its own PEP or could 695 introduce it as well?
erictraut commentedon Jul 24, 2022
HKT support would definitely require its own PEP. It shouldn't be tied to PEP 695 other than to make sure that the two compose and there's a plan for how they work together if both are accepted. HKT support will require significant spec'ing, refining, socializing, and prototyping.
nekitdev commentedon Jul 24, 2022
I'm open to work on such a PEP, but it definitely needs lots of discussions on its specification and implementation. Honestly, 696 seems like a nice number for it, either way! :)
Gobot1234 commentedon Jul 24, 2022
696 is already taken by yours truly 😉
JelleZijlstra commentedon Jul 24, 2022
Right, 696 is going to be typevar defaults. I may be willing to sponsor a PEP for HKT, but given the complexity of the feature I'd like to see a prototype in a major type checker first, as well as a well-specified draft PEP.
nekitdev commentedon Jul 24, 2022
Alright, I'm going to work on the PEP along with the implementation in mypy (though contributors are definitely welcome, haha) type checker.
AlexWaygood commentedon Jul 24, 2022
I'd be happy to help co-write or proofread a HKT PEP. It's a feature I'm quite interested in.
I agree with the other voices in this thread, however, that this would be a significant new feature with a high degree of complexity, that we'd need to take a large degree of care over.
nekitdev commentedon Jul 24, 2022
Okay, I'm going to fork peps repository and add anyone willing to participate as contributors. For discussions, we can either use GitHub Discussions or, alternatively, one can contact me on Discord at
nekitdev#3556(link to my server in case my name changes).jab commentedon Jul 25, 2022
Thank you @nekitdev, @AlexWaygood, @JelleZijlstra, @erictraut, et al., this is exciting progress! If the bidict example should make it into the PEP as one of the motivating examples and there's anything I can do to help with that, please don't hesitate to let me know. And likewise let me know if there's anything I can do to help with testing any mypy or pyright reference implementations of this against a version of bidict that makes use of the new feature.
mscuthbert commentedon Dec 8, 2022
It looks like the work on the new PEP has gotten a little stalled?
Just to give yet another example, music21--the most used library for processing musical scores--has both a number (n) of different ways of iterating across musical scores (parts first, position first, etc.) all of which are subclasses of the main StreamIterator class and use the same iter method, and then a number (m) of different ways of filtering which objects are returned (notes only, metadata only, custom classes, etc.) almost all of which all subclasses of the same StreamFilter class and use the same filter function. As I've been working to extensively type the library, it means that I've had to go from 2 methods that work well together to min(m, n) functions to get the correct typing. (To make matters worse, there are 3
@overloadversions of each method). The lack of HKT is preventing me from wanting to implement new iteration and filtering methods in the next version. It would be great to get something like theargs=1syntax working together with the PEP695 proposals. Happy to help with the writing, though most implementation details might be beyond my understanding of the corner-cases of Functor world.nekitdev commentedon Dec 9, 2022
The new PEP was indeed stalled for a while. I'll try to work on it more in the future, though I'm quite busy with my university atm. Thanks for providing another example, though! Will definitely consider adding it to the PEP.
nekitdev commentedon Dec 10, 2022
So, just to be sure, below is what syntax I'm currently planning.
In classes (and their methods), without PEP 695:
In functions, without PEP 695:
In classes (and their methods), with PEP 695:
In functions, with PEP 695:
PEP 695 authors may need to consider implementing functionality
that would count the number of type arguments (
args) based on the definition, for instance:typeargsIterable[T]1Dict[Q, T]2Tuple[T, U, V]3It is worth noting that that definitions like
Dict[Q]should be rejected by type checkers because of missing arguments.Also there should be a change in how type checkers treat generics in expressions like
type(item)(...).mscuthbert commentedon Dec 11, 2022
What is "Unary" signifying in this case? and is
attrsnecessary to explain the example (for those of us who don't use it, it seems to obfuscate rather than explain. -- does "classes with PEP 695" work just as well if written as:I like this, but I can see the objection that Self already implies "Option[T]" so "Self[T]" looks like "Option[T][T]" what about considering something like this if objections are raised:
other terms could be NonGeneric[Self][U] (if the first syntax is allowed) or ChangedGeneric[Self, [U]] (for the second). I sort of feel that the process of stripping and replacing the GenericType should be explicit.
joelberkeley commentedon Dec 11, 2022
I agree about using
attrs. Would much prefer keeping to the stdlib.Can I also suggest avoiding calling classes (such as
type(self)(...)) in the examples because I'm almost certain that's a very significant hole in Python's type system that could cause confusion when defining the PEP?I'm interested in helping with the PEP but need to see how much time I have
nekitdev commentedon Dec 12, 2022
I agree with skipping the
attrsusage in favor of the standard library. I'm simply too used to the library :pAs for the
type(self)(...), it's arguably the reason the PEP exists — to note that we want to be generic over type constructors.joelberkeley commentedon Dec 13, 2022
I don't follow. The signature of
type(self)(...)is defined by the__init__method, which can be essentially anything. The "type contructor"-ness is the application of the class as a type-level thing to types e.g.type -> typeorFoo[int], rather than the application of the class as an object constructorint -> FooorFoo(1), or indeedint -> bool -> str -> FooandFoo(0, True, "").The hole I'm referring to is (and I may get this example wrong first time as I'll try to use your example rather than how i'm used to presenting it)
which in python typing is valid, but
mapbut willTypeErrordue to an incorrect number of arguments. Where would the type checker raise an error there? My guess is it has to be attype(self)(...)because who knows what signaturetype(self)will have once subclassed.Gobot1234 commentedon Dec 13, 2022
I'd like to share one of my use cases for HKT which I don't think has really been considered
Item's are in a user's inventory and you can't send trades for items that are in another user's inventory. A common error that I see could be solved with HKT is swapping around the sending and receiving arguments and HKT could enforce at type checking time that you don't have this issue. The code would look something like
then using it
Just chiming in here, I think this is a bad idea (for now) for multiple reasons.
Other than that I really like the syntax and can't wait for this to be implemented, keep up the good work :)
schuelermine commentedon Dec 15, 2022
I would find this proposal very useful.
parched commentedon Dec 16, 2022
The original comment mentions Scala and Haskell reference implementations, but what I really want is just generic bounds like even the mainstream Java
and C# have
joelberkeley commentedon Dec 16, 2022
I agree that would be useful, but I don't believe it's higher-kinded types. There are no typevars that take another typevar there. Your example is rather a limitation on how python does typevar bounds. It's similar to this SO post of mine.
What might be confusing here is that the title says "Higher-Kinded TypeVars", but the description says "aka type constructors, generic TypeVars". I don't think this is technically correct. Higher kinded types aren't the same as type constructors. Python already has type constructors e.g.
list, atype -> type. A higher-kinded type is a type that abstracts over a type constructor. We'll need to be clear about this in the PEPparched commentedon Dec 16, 2022
Ok, would it be worth having a separate PEP just for generic bounds? It looks like most of the Mypy issues that link here are actually just asking for generic bounds not HKT. This one also. I wonder how many of the 👍 on this issue are just for generic bounds.
schuelermine commentedon Dec 16, 2022
You linked my issue, so I’d like to chime in again: I believe my issue is a different thing than this issue, as stated by @joelberkeley. I also voiced my approval of this issue before. I want to make clear that this was not mistaken, I want both to be implemented.
qexat commentedon Apr 10, 2023
It's been more than 5 years since this issue was opened. What is its current state? I'm very interested, because I've faced more than one situation where HKTs were required to properly annotate the code I've written. Thank you in advance to anyone who will enlighten me.
Gobot1234 commentedon Apr 10, 2023
If you want to help, you might be able to contribute to https://github.com/nekitdev/peps
mscuthbert commentedon Apr 12, 2023
Specifically the file there is pep-9999.rst
Try to have higher kinded types, but this is not supported yet by pyt…
Asaurus1 commentedon Sep 23, 2023
+1 here for generic bounds. I am running into issues with a project right now that would be easily solved with them.
vnmabus commentedon Nov 16, 2023
I just wanted to say that until this is fixed, it would be impossible to type any library that makes use of the array API standard and has functions that are generic not only on the array type, but also on the dtype or shape parameters. E.g.:
What I try to say is that this is not a marginal use case, and will likely become more ubiquitous as more array-related libraries start following the standard.
smheidrich commentedon Dec 19, 2023
I don't know if everyone saw, but generic TypeVar bounds have a(nother?) dedicated issue at #1226 and have also been implemented in Mypy fork basedmypy. Maybe some of the people who only need generic TypeVar bounds and not the full power of HKTs¹ want to get involved there.
¹ Roughly, as far as I understand it: When you only want to annotate some things as
T[U]while referring toUby itself elsewhere in the same signature, but don't need to have a different parametrizationT[W]in it as well (T,UandWall being TypeVars andThaving a generic bound).rianhunter commentedon Dec 21, 2023
+1 for this feature. I can do this easily in C++ or Haskell:
If there is another way to do this that's currently supported that would be great.
omer54463 commentedon Jan 10, 2024
According to PEP-695, which proposed the new (3.12) generics API:
Any idea why? Generic bound types seem absolutely logical (I even assumed it was possible before trying it and getting disappointed).
The reason it isn't supported yet might be relevant here.
erictraut commentedon Jan 10, 2024
PEP 695 was intended to provide a new syntax for existing mainstream typing features. The PEP was already quite large and involved (with nearly a year of planning that went into it). It didn't make sense to introduce new orthogonal features as part of the PEP.
Almost all modern languages support generics, but very few support higher-kinded types. HKTs are an advanced (and somewhat esoteric) capability that can be useful in certain situations, but they add significant complexity to the type system.
omer54463 commentedon Jan 11, 2024
Correct me if I'm wrong, but the example by @rianhunter should be easily achievable in most languages that support generics.
Is it a simple, specific case of HKT? Because according to references to this issue from other issues, that's most of the demand.
antonagestam commentedon Jan 11, 2024
@omer54463 I don't know what would be specific about it, but it's also hard to tell what the expectations are. If the expectation is for the return type of Foo.bar to somehow vary over A, it's not clear to me with the syntax given how it's expected to vary. In my opinion it's an incomplete example because it doesn't show what the expectations are on usage of these classes.
Perhaps it would be useful to instead provide an actual working piece of C++ to show the capability you mean to compare with?
omer54463 commentedon Jan 13, 2024
@antonagestam Sure!
Here is a simple C++ program to demonstrate the issue:
The call to
non_interface_methodworks because we don't just haveT = int- we haveI = Counter<int>too.This way, the member's type,
Counter<int>, is fully known.This isn't possible in python:
container.get()returnsInterface[int], and not the specific type that implementsInterface[int], so we don't have any methods except for the interface.It should be possible if we allow something like this:
omer54463 commentedon Jan 13, 2024
In addition, even
Typevar("I", bound=Interface[int])is unallowed!This is a very weird restriction - there is nothing generic about
Interface[int]- all of the parameters are resolved.antonagestam commentedon Jan 13, 2024
@omer54463 With some minor changes this code passes mypy: https://mypy-play.net/?mypy=latest&python=3.12&gist=e88f88086056aa61c8d8d2c3c0678d8a. Note that it respects the specialization in the TypeVar.
omer54463 commentedon Jan 14, 2024
@antonagestam Woah, it seems my second comment is incorrect. I had no idea.
I saw your comment and literally got out of bed to check it out.
Unfortunately,
TypeVar("I", bound=Interface[T])or any similar trick I could think of still doesn't work.For example:
Is there any complicated reason for it to not be supported? Any HKT rabbit hole we must end up going down to solve all edge cases?
sebastian-philipp commentedon Jan 15, 2024
@rianhunter , I think, we're close already: https://mypy-play.net/?mypy=latest&python=3.12&gist=e88f88086056aa61c8d8d2c3c0678d8a
rianhunter commentedon Jan 15, 2024
No, you can bind
Protocol[concrete_type]to a type var but notProtocol[type_var]jorenham commentedon Jan 15, 2024
Like @sobolevn mentioned almost 4 years ago, the closest you can get to a solution, is by using the approach that's used in
python-returns:Their approach to e.g.
is to wrap everythin in "containers" (i.e. monads, but don't say that out loud, monads must remain esoteric for some reason):
However, this requires a mypy plugin to work, so it almost surely will not work with e.g. pyright.
See https://returns.readthedocs.io/en/latest/pages/hkt.html for more info.
omer54463 commentedon Jan 16, 2024
It would work. It's also pretty ugly and counterintuitive. I really don't want to introduce returns into an existing company's codebase out of fear of the termination of my employment.
I'm suggesting making the syntax I mentioned a supported feature - is this not an appropriate place for that?
jorenham commentedon Jan 16, 2024
@omer54463 Perhaps I wasn't clear about this, but I meant to show that the currently best "solution" to HKT will probably require significant changes to your codebase, requires you to use mypy and a third-party mypy-plugin, and results in verbose and difficult to read code.
The syntax you mentioned earlier uses
typing.TypeVar, instead of the new PEP 695 syntax.I guess that the desireable HKT syntax would look like either
or some combination of the two.
omer54463 commentedon Jan 18, 2024
@jorenham I roughly agree about the syntax.
However, there is still a distinction I want to make:
Your intentions seem to be to simulate something like this:
This makes no sense to me.
T can be any type, not all types receive type arguments.
I am not saying this could not be checked and supported, but it's a much bigger feature.
However, look at this.
It's trivial that P gets a generic parameter. It's not because we dictate that when using P, but because the bound type has to be generic.
Is P technically an HKT? I'm pretty sure it is. But it's a much simpler case.
jorenham commentedon Jan 18, 2024
@omer54463 I don't know what you mean with the first example. It makes me suspect that you don't understand PEP 695, so let me "backport" my previous example into the pre- PEP 695 era:
The explicit
TypeVarsyntax here, is just an illustrative example I came up with from the top of my head.In your last example,
Pdenotes the parameter that is bound to some kind of higher type, so I'd consider its use inf: P[T] -> ?as HKT.jorenham commentedon Jan 18, 2024
A slightly more involved, realistic example of what HKT could look like in Python:
This example made me realise that the "explicit notation" from my earlier post isn't going to work.
I can think of two "explicit notations":
map_async[R, T: Awaitable[V]](fn: Maps1[V, R], aval: T[V]): T[R]-Voccurs in bothfnandaval, but is bound toV@Awaitablemap_async[V, R, T: Awaitable[V]](fn: Maps1[V, R], aval: T[V]): T[R]-Vis bound toV@Awaitableand to itself, implying equivalence ofT[V]andT[R], butVandRneed not be equivalentBoth cases are self-contractictory, making my earlier proposal for an "explicit notation" a bad idea.
This leaves the "implicit notation", i.e. which I used in this post's example (i.e.
map_async[V, R, T: Awaitable](fn: Maps1[V, R], aval: T[V]) -> T[R]).As far as I can tell, it doesn't have any of the problems that the explicit variant has.
Additionally, it has the benefit that there is only one obvious to do it (and coincidentally, I'm dutch).
omer54463 commentedon Jan 19, 2024
We can support an explicit syntax similar to the ones you mentioned without contradictions.
[X, Y, T: Awaitable], we can useT[X],T[Y], and so on. Let's call this "implicit".[X, T: Awaitable[X]]we can useT, butT[X]makes no sense anymore. Let's call this "explicit".The signature of the function you gave would then be either:
def map_async[V, R, A: Awaitable](fn: Maps1[V, R], aval: A[V]) -> A[R]: ...- implicit, same Awaitable.def map_async[V, R, AV: Awaitable, AR: Awaitable](fn: Maps1[V, R], aval: AV[V]) -> AR[R]: ...- implicit, different Awaitable.def map_async[V, R, AV: Awaitable[V], AR: Awaitable[R]](fn: Maps1[V, R], aval: AV) -> AR: ...- explicit, different Awaitable.Though I am not sure "explicit" is the right term here.
jorenham commentedon Jan 19, 2024
@omer54463 Your 2nd and 3rd signature specifications with 4 type parameters are invalid.
To see why, consider the
map_async(str, asyncio.Task[int])example:Maps1[V, R]matchesstr: Any -> strVbinds toAnyRbinds tostrAV[V]binds toasyncio.Task[V]Vbinds toint, because andint <: AnyAR[R]is narrowed toAR[str]ARcannout be boundSo a type checker has no way of inferring the type of
AR, unless you considerAwaitableto be its default, in which case it will always return anAwaitable[R], which defeats the purpose of HKT in this example.smheidrich commentedon Feb 9, 2024
The important points about the status of this feature and how to contribute have scrolled out of GitHub's very limited window of comments visible on page load by now, which makes them hard for newcomers to this issue to find. So here is a summary again:
pepsrepo. The stub PEP draft so far contains a few examples of the proposed syntax.Maybe @tek or someone else who is allowed to edit it can put this in the original issue so new arrivals find it quickly?
JelleZijlstra commentedon Feb 9, 2024
Good point, I'll put this summary in the top post.
Badg commentedon Mar 5, 2024
I wanted to post this on the linked PEP draft, but I'm getting a 404. Did it maybe move? (cc: @nekitdev)
At any rate, I'm pretty sure I have another use case. I'm currently writing an asset management system that supports the idea of presentation variants, and each presentation variant has a metadata dict. However, the exact format of the metadata dict is determined by the kind of one of the arguments to its constructor. More specifically, I've implemented multiple asset builder classes, which all inherit from a base
Builder, and may then themselves be subclassed further. Each of theBuildersubclasses define their own required metadata keys, but thePresentationVariantInfois generic with respect to the actualBuildersubclass. Simplifying a lot, it looks something like this:However, what I really want is something like this:
Where the type of the metadata can reference the kind of the nested generic B.
Unnecessary details:
Actually, in my case, I actually need both HKT and an intersection type (cross ref here, though this is a different example I didn't include there). In reality, the AMS supports both a build step (always static for any given asset version, for example, reading a template file into memory) and a render step (actually rendering out the asset, for example, rendering a jinja template). Because both the builder and the renderer specify metadata keys, I really need the intersection of both of their typed dicts. So in that case it would look something like this:
nekitdev commentedon Mar 5, 2024
Hey! Yeah, sorry, I messed up sync with the upstream so had to reclone. I'll push the draft shortly.
Edit: here is the updated link.