Skip to content

Add stubs for major protocols #20

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

Closed
wants to merge 0 commits into from
Closed

Conversation

NeilGirdhar
Copy link
Contributor

No description provided.

Copy link
Collaborator

@nstarman nstarman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a great start!
Thanks @NeilGirdhar.
Maybe we can get @34j to comment on how automatic generation might integrate with this project. I imagine that filling out ArrayNamespace would be the prime target.

@NeilGirdhar
Copy link
Contributor Author

Maybe we can get @34j to comment on how automatic generation

100% agree. I thought about it, but I wasn't sure how it would work.

I imagine that filling out ArrayNamespace would be the prime target.

That makes sense!

@jorenham jorenham self-requested a review June 11, 2025 19:53
@nstarman
Copy link
Collaborator

@NeilGirdhar can we wait to merge this in until #18 is in? Hopefully we can get some CI up and running to test these additions :).

@NeilGirdhar
Copy link
Contributor Author

Sounds great, thanks for the thorough review! I know that @jorenham wants to review this as well 😄

@jorenham
Copy link
Collaborator

jorenham commented Jun 13, 2025

Sounds great, thanks for the thorough review! I know that @jorenham wants to review this as well 😄

Yea that's right, thanks for the ping. I'll have a look at it soon.

Copy link
Collaborator

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

Thanks for this PR!

This PR looks pretty similar to what I tried to build before in optype: https://github.com/jorenham/optype/tree/array-api/optype/array_api. There, I also started with the same enthusiasm as I see here. After spending a good amount of time on it, I realized that none of the protocols would be able to solve any problem in practice. So I'm a bit worried that this will happen again.

One of the mistakes I made, was that I started without being able to "see" anything: I should have instead gone with a test-driven approach. Because without integration tests you can't tell whether the relevant libraries will be compatible with the protocols. And in this optype case, it later realized none of the relevant libraries were compatible.

The other big mistake I made, is that I started without knowing the exact problem that I was trying to solve. This is something I still tend to underestimate, so I know how difficult it is.

And as hypocritical as it might sound, that's why I think we should first figure out what exactly the problems are that we're trying to solve with array-api-typing.

Comment on lines 6 to 8
@final
class Device(Protocol):
pass
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it would be useful to be able to structurally type a device, but I don't think that that's possible given the current spec. Right now, this protocol is equivalent to Device = object. And since I don't see any way how we could expand it later on, I think it might be better to leave the the Device out. Because I'm worried that this object alias will give users a false sense of type-safety.

Copy link
Contributor Author

@NeilGirdhar NeilGirdhar Jun 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point about type-safety illusions, but I think we should at least have Device = object so that other libraries have something to point to. Even without type safety, just writing xpt.Device is a nicer annotation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I do in my code when I need to annotate a Jax random number generator key. I find that key: KeyArrayis a lot easier to read that key: Array. And, in theory, if we ever get dtype support for Jax array annotations, then we make KeyArray = jax.Array[KeyDType].

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm wrong here, but I think that the spec for dtypes is too broad for us to be able to structurally describe it. So my previous comment for Device also applies here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, let's discuss this along with Device.


def asarray(
self,
obj: Array | complex | NestedSequence[complex] | SupportsBufferProtocol,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty heavy restriction. Because this will now reject your asarray method unless Array | complex | NestedSequence[complex] | Buffer is assignable to the type of your obj. That's because function domain types are contravariant, which is a consequence of the Liskov Substitution Principle (LSP).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, that's the promise that's shown on the Array API documentation. Did you want a narrower interface?

Comment on lines 33 to 46
def astype(
self,
x: A,
dtype: DType,
/,
*,
copy: bool = True,
device: Device | None = None,
) -> A: ...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if dtype is passed, the array type A will be unaffected. That, when combined with the invariance of A, means that we will not (and cannot) support array types with generic dtype parameter. I'm not sure if that's what we want.

Copy link
Contributor Author

@NeilGirdhar NeilGirdhar Jun 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a really good point. What I hoped for was that:

x: jax.Array
xp = array_api_compat.array_namespace(x)
y = xp.asarray(0.0)
reveal_type(y)  # jax.Array

Is there a way to have that without also promising that the dtype of x and y are the same?

I guess you could regenerate the entire set of typing stubs for each backend. Is that what we need to do?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you choose to make this a package instead of a module?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure if we would end up with more modules. But you're right, YAGNI, so should I switch it to a module?

@NeilGirdhar
Copy link
Contributor Author

NeilGirdhar commented Jun 14, 2025

This PR looks pretty similar to what I tried to build before in optype: https://github.com/jorenham/optype/tree/array-api/optype/array_api. There, I also started with the same enthusiasm as I see here. After spending a good amount of time on it, I realized that none of the protocols would be able to solve any problem in practice. So I'm a bit worried that this will happen again.

One of the mistakes I made, was that I started without being able to "see" anything: I should have instead gone with a test-driven approach. Because without integration tests you can't tell whether the relevant libraries will be compatible with the protocols. And in this optype case, it later realized none of the relevant libraries were compatible.

Yes, this is one of the main issues with the design decision to design the libraries first and the annotations second. If we had done it the other way around with the interfaces first and then have the libraries explicitly inherit from the interfaces, there wouldn't be a problem.

However, I think we already have access to an easy solution. We don't need the libraries to conform to the protocols. We just need the interface returned by array-api-compat to do so. If there are any inconsistencies (between protocol and library), these can be shimmed in array-api-compat.

From there, we can apply soft pressure on the libraries to conform more so that the shims slowly disappear.

What do you think?

The other big mistake I made, is that I started without knowing the exact problem that I was trying to solve. This is something I still tend to underestimate, so I know how difficult it is. And as hypocritical as it might sound, that's why I think we should first figure out what exactly the problems are that we're trying to solve with array-api-typing.

Yes, that makes sense. We're not writing code for its own sake.

For me, I would like my efax library to become typed. Currently, I've typed the arrays as jax.Array, but it's almost entirely xpt.Array. I would like to switch it. That means annotating all of my xp variables as xpt.ArrayNamesapce, and then hopefully xp.asarray, xp.sin, etc. will all be typed.

Also, I think it means that array_api_compat.array_namespace(array) where array: A needs to return ArrayNamespace[A].

This is why I simply lifted the annotations from array-api-compat here. Once we have annotations for ArrayNamespace, etc. here, hopefully we can just point the annotations from array-api-compat into array-api-typing as we discussed here and elswewhere.

What do you think?

@nstarman
Copy link
Collaborator

Sorry @NeilGirdhar. Rebase required.

@NeilGirdhar
Copy link
Contributor Author

Okay, I'm sorry, but I have to make a new PR. I'll link it here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants