Array API support with lazy evaluation

At the moment, our Array API integration can implicitly assume eager evaluation semantics.

Furthermore we did not test our code to see how it would behave with candidate Array API implementations with lazy evaluation semantics (e.g. dask, jax, ...).

The purpose of this issue is to track what would be needed to make our Array API estimator work with lazy evaluation.

I think we should first investigate where this breaks, then decide whether or not we would like to support lazy evaluation semantics in scikit-learn via the Array API support and if so open a meta-issue to add common test with an estimator tag and progressively fix the Array API estimators to deal with lazy evaluation.

Note that are particular point about lazy vs eager evaluation for Array API consuming libraries is being discussed here:

- https://github.com/data-apis/array-api/issues/642

COMMENT:
This was discussed a little in #28588. Here's the writeup detailing our conversation there.

**Summary**

The major issue with lazy array API implementations in scikit-learn, are that after certain operations (e.g. boolean masking,  unique()), the lazy array will end up with a NaN shape, as the shape of the array resulting from a data dependent depend operation is unknown until computation is performed.

This is allowed for in the array API spec, as it allows dimensions to be ``None`` in the shape.
(xref https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.shape.html#shape)

However, becuase scikit-learn depends on the shape being a valid numerical value, this can lead to silent correctness problems or outright errors in scikit-learn, depending on what the shape is used for.

As of right now, scikit-learn currently uses ``.shape`` in order to:

1) Allocate a result array with shape matching the shape of the inputs in at least 1 dimension
2) Using the length of the array to do e.g. normalization
3) Check shapes, raising a ValueError when the shape does not match expectations

(relevant Github code search: https://github.com/search?q=repo%3Ascikit-learn%2Fscikit-learn+.shape+path%3A%2F%5Esklearn%5C%2F%2F&type=code)

Normally, when writing code for e.g. dask, or any other lazy implementation, users can call ``compute`` (or the equivalent function in their library), to perform computation so that the shape is known, before it is needed.

This is not possible with the Array API, though, as there is no method/function to materialize lazy arrays in the Array API.

Furthermore, figuring out when ``compute`` needs to be called before/after an operation is non-trivial.

Current failures

- [ ] PCA ``score`` method
   -  Reason: no ``slogdet`` in dask
   - Tracked in https://github.com/dask/dask/issues/11042
- [ ] `LinearDiscriminantAnalysis`
   -  Reason: ``unique`` is called in this estimator resulting in data-dependent output shapes (represented by nan in dask).
   - Potential solution 1/2 would fix this estimator
- [ ] `r2_score`
   - Reason: An array with data-dependent output shapes (from masked ``__getitem__``) are passed as operands to dask's ``__setitem__``.
   - Potential solution 2 would probably fix this issue 
      - (if we wrap accesses to the ``shape`` attribute to materialize, dask would not complain about the nan shapes)
- [ ] `is_multilabel`
   - Reason: Call to ``unique`` made resulting in data-dependent output shapes (represented by nan in dask)
   - Potential solution 1/2 would fix this method


**Motivation**

Lazy Array API implementations can provide significant speedups by optimizing computation.
(e.g. only compute on the parts of the input array that are needed)

Note: dask implements a limited subset of array optimizations.

**Potential Solutions**

1) ``shape()`` helper

To be upstreamed into ``array-api-compat``

Calls compute if necessary on the array before returning a non-null shape.

(Although ``compute`` is not part of the array API, array-api-compat can call the library specific version of ``compute`` for the lazy array implementations that it knows)

Note: This doesn't work in all cases. 
Sometimes there are methods, where the inputs needs to be materialized beforehands.

2) Wrap array classes in ``array-api-compat`` and override the ``shape`` property and other methods to implicitly materialize their inputs if necessary

Labels: RFC, Array API