Skip to content

Conversation

nstarman
Copy link
Collaborator

@nstarman nstarman commented Jul 1, 2025

Requires #32. I'll rebase when that's in.

Now preceding #32.

@nstarman
Copy link
Collaborator Author

nstarman commented Jul 1, 2025

@jorenham @NeilGirdhar @lucascolley this doesn't resolve the current discussion re a DTypeT typevar or a DType protocol, but does mean Arrays can now understand any typevar or protocol we want to add.

Copy link
Member

@jorenham jorenham left a 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.

@nstarman
Copy link
Collaborator Author

nstarman commented Jul 2, 2025

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.

What do we do about docstrings?

One nit is that ... are not needed when there's a docstring, which already counts as an expression or statement or something.

Yes, my pylint is complaining. But IMO empty methods should have ... to distinguish them from ones that aren't empty. Helps in understanding inheritance with a Protocol.

@jorenham
Copy link
Member

jorenham commented Jul 2, 2025

What do we do about docstrings?

Hmm good question. Maybe a numpy-esque add_docstring function?

@jorenham
Copy link
Member

jorenham commented Jul 2, 2025

Yes, my pylint is complaining. But IMO empty methods should have ... to distinguish them from ones that aren't empty. Helps in understanding inheritance with a Protocol.

Yea I guess there's something to be said for that. It just looks a bit weird to me to see a ... occupy a line on its own (but that might have something to do with me spending most of my breathing time looking at stubs).

@nstarman nstarman force-pushed the has_x_attributes branch 2 times, most recently from 8096aff to 3793c1f Compare July 21, 2025 15:56
@nstarman nstarman force-pushed the has_x_attributes branch 5 times, most recently from cb3dbb3 to dec1842 Compare July 23, 2025 21:05
@nstarman nstarman marked this pull request as ready for review July 23, 2025 21:10
@nstarman nstarman requested a review from jorenham July 23, 2025 21:10
@nstarman
Copy link
Collaborator Author

@jorenham I added tests and made the protocols public.

@nstarman
Copy link
Collaborator Author

@jorenham This should cover all the array attributes.

@nstarman
Copy link
Collaborator Author

nstarman commented Aug 1, 2025

@jorenham it might be easier to merge this before doing numpy type compat stuff from #32.

@nstarman nstarman added this to the v2021-12-0.0 milestone Aug 1, 2025
@nstarman nstarman added ✨ feature Introduce new features. ✅ tests Add, update, or pass tests. labels Aug 1, 2025
...


class HasSize(Protocol):
Copy link
Member

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?

Copy link
Collaborator Author

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?

Copy link
Member

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.

Copy link
Collaborator Author

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]: ...

Copy link
Member

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

Copy link
Member

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 with HasShape.

This library isn't the venue for changing the Array API...

I know, and I wasn't suggesting anything like that.

Copy link
Collaborator Author

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?

Copy link
Collaborator Author

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.

Copy link
Member

@jorenham jorenham Oct 3, 2025

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).

Copy link
Collaborator Author

@nstarman nstarman Oct 3, 2025

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.

HasNDim,
HasShape,
HasSize,
HasTranspose,
Copy link
Member

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

Copy link
Collaborator Author

@nstarman nstarman Aug 18, 2025

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.

Copy link
Member

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.

Copy link
Collaborator Author

@nstarman nstarman Aug 22, 2025

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."

  1. That it statically determines the shape. This focuses on the 1st sentence.
@property
def T(self: SomeArray[Rank2, DTypeT]) -> SomeArray[Rank2, DTypeT]: ...
  1. 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):
Copy link
Member

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.

Copy link
Collaborator Author

@nstarman nstarman Aug 18, 2025

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...

Copy link
Member

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.

Copy link
Collaborator Author

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.

Copy link
Member

@jorenham jorenham Aug 22, 2025

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.

Copy link
Collaborator Author

@nstarman nstarman Sep 23, 2025

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.

Copy link
Collaborator Author

@nstarman nstarman Sep 23, 2025

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.

Copy link
Collaborator Author

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?

#22 (comment)

Copy link
Member

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?

#22 (comment)

What methods, exactly?

Copy link
Collaborator Author

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.

@nstarman nstarman requested a review from jorenham August 20, 2025 01:26
@nstarman
Copy link
Collaborator Author

nstarman commented Aug 22, 2025

@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.
If we can agree to have method-by-method Protocols for https://data-apis.org/array-api/2021.12/API_specification/index.html#api-specification-index--page-root then we can pause on making Array while we more quickly build out the underlying protocols.
I imagine there will be some revisions to the individual Protocols necessary when stitching them together into various Array-like protocols that we didn't see when writing them individually, but I think these are separable steps and concerns and we should fill out the namespace ASAP.

@nstarman nstarman mentioned this pull request Aug 22, 2025
@nstarman nstarman changed the title feat: HasX attributes ✨: HasX attributes Aug 22, 2025
@nstarman
Copy link
Collaborator Author

nstarman commented Sep 3, 2025

@jorenham what do you think?

@nstarman
Copy link
Collaborator Author

nstarman commented Sep 3, 2025

I'm removing the atomic protocols from Array and reworking the tests accordingly.

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]>
@nstarman
Copy link
Collaborator Author

@jorenham what do you think?

@nstarman
Copy link
Collaborator Author

Ping @jorenham

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ feature Introduce new features. ✅ tests Add, update, or pass tests.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants