===========================================================
NEP 35 — Array Creation Dispatching With __array_function__
===========================================================

:Author: Peter Andreas Entschev <pentschev@nvidia.com>
:Status: Draft
:Type: Standards Track
:Created: 2019-10-15
:Updated: 2019-10-15
:Resolution:

Abstract
--------

We propose the introduction of a new keyword argument ``like=`` to all array
creation functions to permit dispatching of such functions by the
``__array_function__`` protocol, addressing one of the protocol shortcomings,
as described by NEP-18 [1]_.

Detailed description
--------------------

The introduction of the ``__array_function__`` protocol allowed downstream
library developers to use NumPy as a dispatching API. However, the protocol
did not -- and did not intend to -- address the creation of arrays by downstream
libraries, preventing those libraries from using such important functionality in
that context.

Other NEPs have been written to address parts of that limitation, such as the
introduction of the ``__duckarray__`` protocol in NEP-30 [2]_, and the
introduction of an overriding mechanism called ``uarray`` by NEP-31 [3]_.

The purpose of this NEP is to address that shortcoming in a simple and
straighforward way: introduce a new ``like=`` keyword argument, similar to how
the ``empty_like`` family of functions work. When array creation functions
receive such an argument, they will trigger the ``__array_function__`` protocol,
and call the downstream library's own array creation function implementation.
The ``like=`` argument, as its own name suggests, shall be used solely for the
purpose of identifying where to dispatch.  In contrast to the way
``__array_function__`` has been used so far (the first argument identifies where
to dispatch), and to avoid breaking NumPy's API with regards to array creation,
the new ``like=`` keyword shall be used for the purpose of dispatching.

Usage Guidance
~~~~~~~~~~~~~~

The new ``like=`` keyword is solely intended to identify the downstream library
where to dispatch and the object is used only as reference, meaning that no
modifications, copies or processing will be performed on that object.

We expect that this functionality will be mostly useful to library developers,
allowing them to create new arrays for internal usage based on arrays passed
by the user, preventing unnecessary creation of NumPy arrays that will
ultimately lead to an additional conversion into a downstream array type.

Implementation
--------------

The implementation requires introducing a new ``like=`` keyword to all existing
array creation functions of NumPy. As examples of functions that would add this
new argument (but not limited to) we can cite those taking array-like objects
such as ``array`` and ``asarray``, functions that create arrays based on
numerical ranges such as ``range`` and ``linspace``, as well as the ``empty``
family of functions, even though that may be redundant, since there exists
already specializations for those with the naming format ``empty_like``. As of
the writing of this NEP, a complete list of array creation functions can be
found in [4]_.

This newly proposed keyword shall be removed by the ``__array_function__``
mechanism from the keyword dictionary before dispatching. The purpose for this
is twofold:

1. The object will have no use in the downstream library's implementation; and
2. Simplifies adoption of array creation by those libraries already opting-in
   to implement the ``__array_function__`` protocol, thus removing the
   requirement to explicitly opt-in for all array creation functions.

Downstream libraries thus shall _NOT_ include the ``like=`` keyword to their
array creation APIs, which is a NumPy-exclusive keyword.

Function Dispatching
~~~~~~~~~~~~~~~~~~~~

There are two different cases to dispatch: Python functions, and C functions.
To permit ``__array_function__`` dispatching, one possible implementation is to
decorate Python functions with ``overrides.array_function_dispatch``, but C
functions have a different requirement, which we shall describe shortly.

The example below shows a suggestion on how the ``asarray`` could be decorated
with ``overrides.array_function_dispatch``:

.. code:: python

    def _asarray_decorator(a, dtype=None, order=None, like=None):
        return (like,)

    @set_module('numpy')
    @array_function_dispatch(_asarray_decorator)
    def asarray(a, dtype=None, order=None, like=None):
        return array(a, dtype, copy=False, order=order)

Note in the example above that the implementation remains unchanged, the only
difference is the decoration, which uses the new ``_asarray_decorator`` function
to instruct the ``__array_function__`` protocol to dispatch if ``like`` is not
``None``.

We will now look at a C function example, and since ``asarray`` is anyway a
specialization of ``array``, we will use the latter as an example now. As
``array`` is a C function, currently all NumPy does regarding its Python source
is to import the function and adjust its ``__module__`` to ``numpy``. The
function will now be decorated with a specialization of
``overrides.array_function_from_dispatcher``, which shall take care of adjusting
the module too.

.. code:: python

    array_function_nodocs_from_c_func_and_dispatcher = functools.partial(
        overrides.array_function_from_dispatcher,
        module='numpy', docs_from_dispatcher=False, verify=False)

    @array_function_nodocs_from_c_func_and_dispatcher(_multiarray_umath.array)
    def array(a, dtype=None, copy=True, order='K', subok=False, ndmin=0, like=None):
        return (like,)

There are two downsides to the implementation above for C functions:

1.  It creates another Python function call; and
2.  To follow current implementation standards, documentation should be attached
    directly to the Python source code.

Alternatively for C functions, the implementation of ``like=`` could be moved
into the C implementation itself. This is not the primary suggestion here due
to its inherent complexity which would be difficult too long to describe in its
entirety here, and too tedious for the reader. However, we leave that as an
option open for discussion.

Usage
-----

The purpose of this NEP is to keep things simple. Similarly, we can exemplify
the usage of ``like=`` in a simple way. Imagine you have an array of ones
created by a downstream library, such as CuPy. What you need now is a new array
that can be created using the NumPy API, but that will in fact be created by
the downstream library, a simple way to achieve that is shown below.

.. code:: python

    x = cupy.ones(2)
    np.array([1, 3, 5], like=x)     # Returns cupy.ndarray

As a second example, we could also create an array of evenly spaced numbers
using a Dask identity matrix as reference:

.. code:: python

    x = dask.array.eye(3)
    np.linspace(0, 2, like=x)       # Returns dask.array


Compatibility
-------------

This proposal does not raise any backward compatibility issues within NumPy,
given that it only introduces a new keyword argument to existing array creation
functions.

Downstream libraries will benefit from the ``like=`` argument automatically,
that is, without any explicit changes in their codebase. The only requirement
is that they already implement the ``__array_function__`` protocol, as
described by NEP-18 [2]_.

References and Footnotes
------------------------

.. [1] `NEP-18 - A dispatch mechanism for NumPy's high level array functions <https://numpy.org/neps/nep-0018-array-function-protocol.html>`_.

.. [2] `NEP 30 — Duck Typing for NumPy Arrays - Implementation <https://numpy.org/neps/nep-0030-duck-array-protocol.html>`_.

.. [3] `NEP 31 — Context-local and global overrides of the NumPy API <https://github.com/numpy/numpy/pull/14389>`_.

.. [4] `Array creation routines <https://docs.scipy.org/doc/numpy-1.17.0/reference/routines.array-creation.html>`_.

Copyright
---------

This document has been placed in the public domain.
