-
Notifications
You must be signed in to change notification settings - Fork 5
✨: HasX attributes #34
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
base: main
Are you sure you want to change the base?
Conversation
@jorenham @NeilGirdhar @lucascolley this doesn't resolve the current discussion re a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the Has*
protocols. Optype will cover the CanArray*
ones I think. There might be some that can't be expressed because Self
can't be passed as generic type argument, but I suppose we can deal with it when needed.
One nit is that ...
are not needed when there's a docstring, which already counts as an expression or statement or something.
What do we do about docstrings?
Yes, my pylint is complaining. But IMO empty methods should have |
Hmm good question. Maybe a numpy-esque |
Yea I guess there's something to be said for that. It just looks a bit weird to me to see a |
8096aff
to
3793c1f
Compare
cb3dbb3
to
dec1842
Compare
dec1842
to
53ddb98
Compare
53ddb98
to
a5e3d5b
Compare
@jorenham I added tests and made the protocols public. |
@jorenham This should cover all the array attributes. |
... | ||
|
||
|
||
class HasSize(Protocol): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the use-case of this one? Is there anything this can help with, that HasShape
can't?
Put differently; should we make this public API or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we have Protocols for all attributes in the Array API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why we should write a protocol if there's no use for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def is_sized(obj: Any, /) -> TypeGuard[HasSize]: ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, and what would you use that for in a real-world scenario
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I'm trying to prevent is situations where there are multiple non-obvious ways of achieving the same result. And
HasSize
seems to me like it could have a lot of overlap withHasShape
.This library isn't the venue for changing the Array API...
I know, and I wasn't suggesting anything like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that not the practical effect of omission?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the Array API spec has methods size
and shape
, and the corresponding typing library has only HasShape
then by omission it re-interprets the Array API. Yes people can write their own HasSize
, but the target audience of the Array API isn't just lovers of typing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that not the practical effect of omission?
No? AFAIK, this is library depends on the array api, not the other way around. But maybe I'm being naive here 🤷🏻.
Plus, there are already stub-like types in the array-api itself (https://github.com/data-apis/array-api/tree/main/src/array_api_stubs) that are used for documentation purposes (and don't have any use-case besides that).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They aren't planning on publishing those. As I understand it, we depend on the Array API spec and our remit is to describe it statically. As the official typing library for that spec, omission is change.
We have HasNamespace. We should have similar for the other methods and attributes of Array. We should have useful intersection types related to Array. We should have DoesFunction protocols for the functions and a Namespace protocol for their collection.
src/array_api_typing/_array.py
Outdated
HasNDim, | ||
HasShape, | ||
HasSize, | ||
HasTranspose, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This HasTranspose
restricts this to 2d arrays
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it? I don't know of any array object that doesn't have this method, regardless of the value's dimensionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.transpose()
is only defined for 2d arrays, so we should expect array libs to follow that, and we should therefore expect there to be annotations like
class SomeArray[ShapeT, DTypeT]:
# --snip--
def transpose(self: SomeArray[Rank2, DTypeT]) -> SomeArray[Rank2, DTypeT]: ...
So a SomeArray[Rank1]
won't be assignable to xpt.Array
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds great.
It sounds like there are two interpretations of how this should be represented statically given the API spec: "The array instance must be two-dimensional. If the array instance is not two-dimensional, an error should be raised."
- That it statically determines the shape. This focuses on the 1st sentence.
@property
def T(self: SomeArray[Rank2, DTypeT]) -> SomeArray[Rank2, DTypeT]: ...
- that it shadow the runtime. This focuses on the 2nd sentence.
@overload @property
def T(self: SomeArray[Rank0 | Rank1, DTypeT]) -> Never: ...
@overload @property
def T(self: SomeArray[Rank2, DTypeT]) -> SomeArray[Rank2, DTypeT]: ...
@overload @property
def T(self: SomeArray[RankGE3, DTypeT]) -> Never: ...
(or if we don't have shape typing)
def T(self: SomeArray) -> SomeArray | Never: ... # yes, the Never is ignored.
This was written more with the 2nd interpretation in mind, but I'm very happy to accept the 1st interpretation and remove T
from the default Array object, so long as we still include some form of HasTranspose
Protocol for people to use.
I do think there is then interesting discussion about that Protocol ....
Should we vendor 2 forms? one for each interpretation? That will help people when building their own intersection types.
... | ||
|
||
|
||
class HasNDim(Protocol): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a situation where you'd wanna use HasNDim
over e.g. HasShape
? Otherwise we probably should keep this private, given that
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're checking for the attribute ndim
.
Not sure I'm understanding this comment. Ndim is in the Array API spec...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, my point is that I think we should only provide users with protocols that help them annotate their array-api code. Otherwise, it'll just be confusing for the users, and a waste of time for us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh. As the Array API is the intersection of the array libraries, IMO pretty much everything is useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea of course, everything in the array-api is designed for a reason. But all of those reasons apply in the runtime world. And our goal is to provide an API for the static-typing world, which is but a shadow of the runtime one, where only a subset of the API has practical use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python comes with batteries.
The Array API is the intersection of the array libraries and each method is useful / used by many people / has long historical precedent, which is why it's in all the libraries.
IMO this library isn't to ideologically improve on the Array API, it's to provide the tools to describe it statically.
My suggestion is to have all these low-level protocols. Where we go beyond the baseline Array API is in how these protocols are parametrized and how we build intersection types. This is where necessity drives us to make decisions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the main difference is that almost all of the optype protocols were written with a specific use-case in mind
I don't disagree with this idea, but let's consider the situation. There are numerous array libraries which people have spent a long time working on, thinking about their APIs for many years. Numpy in particular sets much of the standard and has many people thinking about the things in it. They got together to rethink numpy and array libraries in general, to mass adoption. We've been to some of those meetings where they're agonizing over a small piece of API. IMO the presumption of usefulness is very clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jorenham can we get in protocols for the methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jorenham can we get in protocols for the methods?
What methods, exactly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Methods and attributes of Array.
a5e3d5b
to
27ef62d
Compare
Signed-off-by: nstarman <[email protected]>
27ef62d
to
c65c5d5
Compare
@jorenham. I can see your points about some of the dangers of building the Array intersection protocol from all these method-by-method protocols before we've figured out shape typing, etc. |
@jorenham what do you think? |
c65c5d5
to
a45816c
Compare
I'm removing the atomic protocols from |
a45816c
to
c23b17a
Compare
Signed-off-by: nstarman <[email protected]>
Signed-off-by: nstarman <[email protected]>
Signed-off-by: nstarman <[email protected]>
Signed-off-by: Nathaniel Starkman <[email protected]>
Signed-off-by: nstarman <[email protected]>
c23b17a
to
a53c176
Compare
@jorenham what do you think? |
Ping @jorenham |
Requires #32. I'll rebase when that's in.Now preceding #32.