-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for Protocol
s
#28
Comments
I forgot to mention, but there is also a decorator from typing import runtime_checkable
@runtime_checkable
class Named(Protocol):
name: str This snippet causes |
This comment has been minimized.
This comment has been minimized.
EDIT: my comment above is wrong. There are a few problems with supporting Protocols :
A possibility would be to partially re-implement Protocols internally in Plum (pretty much like a few abstract types are implemented now) but that seems like an expensive solution... |
Ah, support for
This is also what I'm thinking would be the way forward, which is not impossible but potentially a quite sizeable chunk of work. If you want to have a go at it (and please feel free to!), the implementation can perhaps be similar to those for What perhaps should happen first is a slight rework of I might not have the opportunity to sort that out and look at support for |
@wesselb I think one major source of complication is that all internal plum types have a meta-class For starters, we could get rid of |
@PhilipVinc I was not aware of One other aspect which is not entirely satisfactory is that Plum types are objects rather than types. For example, |
Yeah, It's very confusing even for me... We should brainstorm a bit about possible solutions to this.
Is this feature really needed? |
I fully agree that it would be much nicer to just work with normal types and avoid wrapping everything.
Yeah, that's right. For example, I might want to write a package which provides different implementations for JAX, TensorFlow, and PyTorch: import lab as B
from plum import dispatch
@dispatch
def my_fun(x: B.JAXNumeric):
# Do something
@dispatch
def my_fun(x: B.TFNumeric):
# Do something slight different
@dispatch
def my_fun(x: B.TorchNumeric):
# Do something in another way These functions would depend on the tensor types of JAX, TensorFlow, and PyTorch. However, to actually depend on those tensor types (in the sense of having a reference to them), you would have to import all three packages (and hence have them installed), which isn't very lightweight. Therefore, it would be nice if you could write the above code where I agree that this is a pretty niche use case, though. A more common and unfortunately more complicated use case is that of forward references: from plum import dispatch
class A:
@dispatch
def do(self, a: "A"):
pass Here Nevertheless, I think these two features can possibly be implemented with just parametric types, which sounds much more appealing. |
Thank you both for your replies! I would love to contribute in any way I can, I'm just really busy until next week. Nevertheless, I am interested in helping here.
I fully agree that re-implementing every
This looks like a good place to start.
@wesselb what would a solution like this look like? Do you mean something like One thing that is puzzling me here is that, just for fun, I erased the function Anyway, if you agree that perhaps some refactoring in the base code is advisable before supporting protocols, I am more than happy to help in this. Do you have any advice regarding where to start? |
That's no problem at all. I'm also fairly busy with study things, so I'm also only working on this intermittently. :) You offering your help is already very much appreciated.
Although this isn't ideal, we might have to reimplement The current ugliness is that, in addition to the reimplementation of some
Suppose that we would really have to reimplement from plum import parametric
@parametric
class MyUnion:
@classmethod
def __infer_type_parameter__(cls, *types):
return types Whereas types with arguments (like a union with types) are currently represented as objects with those arguments stored as attributed, in the above pattern types would be represented as actual types ( The main reason why we cannot just implement all types in the above way is that, in the above pattern, the arguments to a type must be hashable at type construction time. Crucially, more complicated types such as forward references to classes or types which load once a package is imported may not yet be hashable when they are fed as arguments. One way around this is to assign a temporary, unique hash value to types which are not yet hashable and (1) change the hash when the type becomes hashable and (2) reset all caches which rely on that hash. I think this will work.
Could it be that
I'm slightly leaning towards that, before implementing support for |
By the way, I'm not sure the Union presented above makes much sense. Maybe this is something we should keep in mind. |
Hah, yeah, you're totally right. Doesn't make much sense indeed. I guess I intended to use it as a sort of constructor for the type, which we currrently don't have. If a constructor would be necessary, maybe it isn't, one option would be to add another magic method: from plum import parametric
@parametric
class MyUnion:
@classmethod
def __type_parameter__(cls, *types):
# Validate, parse, or process `types` here...
return types I guess it could be useful in general to have a constructor which can validate the type parameters that you pass to a type. E.g., this would be possible: from plum import parametric, dispatch
@parametric
class NTuple:
@dispatch
@classmethod
def __type_parameter__(cls, el_type: type, number: int):
return (el_type, number)
@dispatch
@classmethod
def __type_parameter__(cls, *types: type):
return cls.__type_parameter__(Union[types], len(types)) To prevent types from being instantiated, one way is to raise an exception in the constructor. Perhaps something like the below could be what we might want? from plum import parametric, dispatch
@parametric
class MyUnion:
def __init__(self):
raise TypeError("Unions cannot be instantiated.")
@dispatch
@classmethod
def __type_parameter__(cls, *types: type):
return set(types) |
Instead of implementing custom types so that I believe that one problem here is that a subclass is a class that derives from another one. Thus, in my opinion, it is conceptually wrong to expect There are some libs out there (like import typing
from typing_utils import issubtype
assert issubtype(typing.List[typing.List], typing.List[typing.Sequence]) == True I don't know how well they work with forward references, but perhaps they're worth experimenting?
If wrapping types is the way to go, then I find |
That's a very fair point to make. My take on this is slightly different: I think of the collection as all types simply as an set with a total order implemented by
Ah, that's very nice! Thanks for mentioning this—I'll definitely look into it.
Yay! At the moment, I'm fighting against some deadlines, but, after those have passed, I'll try to put together a proposal of the proposed refactoring. |
any news on this? any way i can help? |
@mofeing Unfortunately on my side there has been no progress for the support of |
Protocol
s were introduced in Python 3.8 by PEP 544, and it would be awesome if Plum could handle them.A simple use case would look like this:
Another use case is what I am actually interested in: using dataclasses. Dataclasses can be identified by a special attribute:
__dataclass_fields__
(see python/mypy#8578), so that I could writeI have no idea how to proceed to implement this, but I'm interested in seeing this in Plum and would love to help in any way I can.
Just for the record, Dry Python Classes has this feature, we could take some inspiration from them.
The text was updated successfully, but these errors were encountered: