Array API backends support for MLX

It would be great to get the scikit-learn Array API back-end to be compatible with MLX (which is mostly conformant with the array API). 

Here is an example which currently does not work for a few reasons:

```python
from sklearn.datasets import make_classification
from sklearn import config_context
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
import mlx.core as mx

X_np, y_np = make_classification(random_state=0)
X_mx = mx.array(X_np)
y_mx = mx.array(y_np)

with config_context(array_api_dispatch=True):
    lda = LinearDiscriminantAnalysis()
    X_trans = lda.fit_transform(X_mx, y_mx)

print(type(X_trans))
```

The reasons it does not work:

- MLX does not have a `float64` data type (similar to PyTorch MPS backend). It's a bit hacky to set `mx.float64 = mx.float32` so maybe good to handle this in the scikit or in a compatibility layer.

- MLX does not support operations with data-dependent output shapes, e.g. [`unique_values`](https://data-apis.org/array-api/2022.12/API_specification/generated/array_api.unique_values.html). Since these are optional in the array API should we attempt to avoid using them in scikit to get maximal compatibility with other frameworks?

- There are still a couple functions missing in MLX like `mx.asarray` and `mx.isdtype` (those are pretty easy for us to add)

Relevant discussion in MLX https://github.com/ml-explore/mlx/pull/1289

CC @betatim

COMMENT:
For the dtype problem, the user should always feed float32 data to scikit-learn if they want to use MLX on Apple Silicon:

```python
import numpy as np
from sklearn.datasets import make_classification
from sklearn import config_context
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
import mlx.core as mx

X_np, y_np = make_classification(random_state=0)
X_np = X_np.astype(np.float32)
y_np = y_np.astype(np.float32)
X_mx = mx.array(X_np)
y_mx = mx.array(y_np)

with config_context(array_api_dispatch=True):
    lda = LinearDiscriminantAnalysis()
    X_trans = lda.fit_transform(X_mx, y_mx)

print(type(X_trans))
```

but that still fails with:

```python-traceback
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[3], line 15
     13 with config_context(array_api_dispatch=True):
     14     lda = LinearDiscriminantAnalysis()
---> 15     X_trans = lda.fit_transform(X_mx, y_mx)
     17 print(type(X_trans))

File ~/code/scikit-learn/sklearn/utils/_set_output.py:319, in _wrap_method_output.<locals>.wrapped(self, X, *args, **kwargs)
    317 @wraps(f)
    318 def wrapped(self, X, *args, **kwargs):
--> 319     data_to_wrap = f(self, X, *args, **kwargs)
    320     if isinstance(data_to_wrap, tuple):
    321         # only wrap the first output for cross decomposition
    322         return_tuple = (
    323             _wrap_data_with_container(method, data_to_wrap[0], X, self),
    324             *data_to_wrap[1:],
    325         )

File ~/code/scikit-learn/sklearn/base.py:921, in TransformerMixin.fit_transform(self, X, y, **fit_params)
    918     return self.fit(X, **fit_params).transform(X)
    919 else:
    920     # fit method of arity 2 (supervised transformation)
--> 921     return self.fit(X, y, **fit_params).transform(X)

File ~/code/scikit-learn/sklearn/base.py:1389, in _fit_context.<locals>.decorator.<locals>.wrapper(estimator, *args, **kwargs)
   1382     estimator._validate_params()
   1384 with config_context(
   1385     skip_parameter_validation=(
   1386         prefer_skip_nested_validation or global_skip_validation
   1387     )
   1388 ):
-> 1389     return fit_method(estimator, *args, **kwargs)

File ~/code/scikit-learn/sklearn/discriminant_analysis.py:661, in LinearDiscriminantAnalysis.fit(self, X, y)
    640 """Fit the Linear Discriminant Analysis model.
    641 
    642 .. versionchanged:: 0.19
   (...)
    656     Fitted estimator.
    657 """
    658 xp, _ = get_namespace(X)
    660 X, y = validate_data(
--> 661     self, X, y, ensure_min_samples=2, dtype=[xp.float64, xp.float32]
    662 )
    663 self.classes_ = unique_labels(y)
    664 n_samples, _ = X.shape

AttributeError: module 'mlx.core' has no attribute 'float64'
```

Because the attribute `xp.float64` does not exist, even we don't actually try to ever use it.

Exposing a fake `xp.float64` symbol in a compat layer might work but feels weird. It might be a pragmatic first step into exploring what else would break in practice once this technical detail is sidestepped.

Labels: New Feature, Needs Investigation, Array API