From 358363e17fc4968161ffa5cf1ad3b76740f1fd80 Mon Sep 17 00:00:00 2001 From: Jake VanderPlas Date: Tue, 13 Sep 2022 09:14:48 -0700 Subject: [PATCH] JEP 12049: Type Annotation Roadmap --- docs/_static/vscode-completion.png | Bin 0 -> 31521 bytes docs/jep/12049-type-annotations.md | 334 +++++++++++++++++++++++++++++ docs/jep/index.rst | 1 + 3 files changed, 335 insertions(+) create mode 100644 docs/_static/vscode-completion.png create mode 100644 docs/jep/12049-type-annotations.md diff --git a/docs/_static/vscode-completion.png b/docs/_static/vscode-completion.png new file mode 100644 index 0000000000000000000000000000000000000000..cd943826694d06a6f9524ec64712572ce391b782 GIT binary patch literal 31521 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 literal 0 HcmV?d00001 diff --git a/docs/jep/12049-type-annotations.md b/docs/jep/12049-type-annotations.md new file mode 100644 index 000000000..3288d252d --- /dev/null +++ b/docs/jep/12049-type-annotations.md @@ -0,0 +1,334 @@ +# Type Annotation Roadmap for JAX + +- *Author: jakevdp* + +- *Date: August 2022* + +## Background + +Python 3.0 introduced optional function annotations ([PEP 3107](https://peps.python.org/pep-3107/)), which were later codified for use in static type checking around the release of Python 3.5 ([PEP 484](https://peps.python.org/pep-0484/)). +To some degree, type annotations and static type checking have become an integral part of many Python development workflows, and to this end we have added annotations in a number of places throughout the JAX API. +The current state of type annotations in JAX is a bit patchwork, and efforts to add more have been hampered by more fundamental design questions. +This doc attempts to summarize those issues and generate a roadmap for the goals and non-goals of type annotations in JAX. + +Why do we need such a roadmap? Better/more comprehensive type annotations are a frequent request from users, both internally and externally. +In addition, we frequently receive pull requests from external users (for example, [PR #9917](https://github.com/google/jax/pull/9917), [PR #10322](https://github.com/google/jax/pull/10322)) seeking to improve JAX's type annotations: it's not always clear to the JAX team member reviewing the code whether such contributions are beneficial, particularly when they introduce complex Protocols to address the challenges inherent to full-fledged annotation of JAX's use of Python. +This document details JAX's goals and recommendations for type annotations within the package. + +## Why type annotations? + +There are a number of reasons that a Python project might wish to annotate their code-base; we'll summarize them in this document as Level 1, Level 2, and Level 3. + +### Level 1: Annotations as documentation + +When originally introduced in [PEP 3107](https://peps.python.org/pep-3107/), type annotations were motivated partly by the ability to use them as concise, inline documentation of function parameter types and return types. JAX has long utilized annotations in this manner; an example is the common pattern of creating type names aliased to `Any`. An example can be found in `lax/slicing.py` [[source](https://github.com/google/jax/blob/2bc3e39cd9104071ee39dacac22abd51b94eb27e/jax/_src/lax/slicing.py#L47-L58)]: + +```python +Array = Any +Shape = core.Shape + +def slice(operand: Array, start_indices: Sequence[int], + limit_indices: Sequence[int], + strides: Optional[Sequence[int]] = None) -> Array: + ... +``` + +For the purposes of static type checking, this use of `Array = Any` for array annotations puts no constraint on the argument values (`Any` is equivalent to no annotation at all), but it does serve as a form of useful in-code documentation for the developer. + +For the sake of generated documentation, the name of the alias gets lost (the [HTML docs](https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.slice.html) for `jax.lax.slice` report operand as type `Any`), so the documentation benefit does not go beyond the source code (though we could enable some `sphinx-autodoc` options to improve this: See [autodoc_type_aliases](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#confval-autodoc_type_aliases)). + +A benefit of this level of type annotation is that it is never wrong to annotate a value with `Any`, so it will provide a concrete benefit to developers and users in the form of documentation, without added complexity of satisfying the stricter needs of any particular static type checker. + +### Level 2: Annotations for intelligent autocomplete +Many modern IDEs take advantage of type annotations as inputs to [intelligent code completion](https://en.wikipedia.org/wiki/Intelligent_code_completion) systems. One example of this is the [Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance) extension for VSCode, which uses Microsoft's [pyright](https://github.com/microsoft/pyright) static type checker as a source of information for VSCode's [IntelliSense](https://code.visualstudio.com/docs/editor/intellisense) completions. + +This use of type checking requires going further than the simple aliases used above; for example, knowing that the `slice` function returns an alias of `Any` named `Array` does not add any useful information to the code completion engine. However, were we to annotate the function with a `DeviceArray` return type, the autocomplete would know how to populate the namespace of the result, and thus be able to suggest more relevant autocompletions during the course of development. + +JAX has begun to add this level of type annotation in a few places; one example is the `jnp.ndarray` return type within the `jax.random` package [[source](https://github.com/google/jax/blob/2bc3e39cd9104071ee39dacac22abd51b94eb27e/jax/_src/random.py#L359)]: + +```python +def shuffle(key: KeyArray, x: Array, axis: int = 0) -> jnp.ndarray: + ... +``` + +In this case `jnp.ndarray` is an abstract base class that forward-declares the attributes and methods of JAX arrays ([see source](https://github.com/google/jax/blob/2bc3e39cd9104071ee39dacac22abd51b94eb27e/jax/_src/numpy/ndarray.py#L41)), and so Pylance in VSCode can offer the full set of autocompletions on results from this function. Here is a screenshot showing the result: + +![VSCode Intellisense Screenshot](../_static/vscode-completion.png) + +Listed in the autocomplete field are all methods and attributes declared by the abstract `ndarray` class. +We'll discuss further below why it was necessary to create this abstract class rather than annotating with `DeviceArray` directly. + +### Level 3: Annotations for static type-checking + +These days, static type-checking often is the first thing people think of when considering the purpose of type annotations in Python code. +While Python does not do any runtime checking of types, several mature static type checking tools exist that can do this as part of a CI test suite. +The most important ones for JAX are the following: + +- [python/mypy](https://github.com/python/mypy) is more or less the standard in the open Python world. JAX currently runs mypy on a subset of source files within the Github Actions CI checks. +- [google/pytype](https://github.com/google/pytype) is Google's static type checker, and projects which depend on JAX within Google frequently use this. +- [microsoft/pyright](https://github.com/microsoft/pyright) is important as the static type checker used within VSCode for the Pylance completions mentioned previously. + +Full static type checking is the strictest of all the type annotation applications, because it will surface an error any time your type annotations are not precisely correct. +On the one hand, this is nice because your static type analysis may catch faulty type annotations (for example, a case where a `DeviceArray` method is missing from the `jnp.ndarray` abstract class). + +On the other hand, this strictness can make the type checking process very brittle in packages that often rely on duck-typing rather than strict type-safe APIs. +You'll currently find code comments like `#type: ignore` (for mypy) or `#pytype: disable` (for pytype) peppered throughout the JAX codebase in several hundred places. +These typically represent cases where typing problems have arisen; they may be inaccuracies in JAX type annotations, or inaccuracies in the static type checker's ability to correctly follow the control flow in the code. +On occasion, they are due to real & subtle bugs in the behavior of pytype or mypy. +In rare cases, they may be due to the fact that JAX uses Python patterns that are difficult or even impossible to express in terms of Python's static type annotation syntax. + +## Type annotation challenges for JAX + +JAX currently has type annotations that are a mixture of different styles, and aimed at all three levels of type annotation discussed above. +Partly, this comes from the fact that JAX's source code poses a number of unique challenges for Python's type annotation system. We'll outline them here. + +### Challenge 1: pytype, mypy and developer friction + +One challenge JAX currently faces is that package development must satisfy the constraints of two different static type checking systems, `pytype` (used by internal CI and internal Google projects) and `mypy` (used by external CI and external dependencies). +Although the two type checkers have broad overlap in their behavior, each presents its own unique corner cases, as evidenced by the numerous `#type: ignore` and `#pytype: disable` statements throughout the JAX codebase. + +This creates friction in development: internal contributors may iterate until tests pass, only to find that on export their pytype-approved code falls afoul of mypy. +For external contributors, it's often the opposite: a recent example is {jax-issue}`#9596` which had to be rolled-back after it failed internal Google pytype checks. +Each time we move a type annotation from Level 1 (`Any` everywhere) to Level 2 or 3 (stricter annotations), it introduces more potential for such frustrating developer experiences. + +### Challenge 2: array duck-typing + +One particular challenge for annotating JAX code is its heavy use of duck-typing. An input to a function marked `Array` in general could be one of many different types: a JAX `DeviceArray`, a NumPy `np.ndarray`, a NumPy scalar, a Python scalar, a Python sequence, an object with an `__array__` attribute, an object with a `__jax_array__` attribute, or any flavor of `jax.Tracer`. +For this reason, simple annotations like `def func(x: DeviceArray)` will not be sufficient, and will lead to false positives for many valid uses. +This means that type annotations for JAX functions will not be short or trivial, but we would have to effectively develop a set of JAX-specific typing extensions similar to those in the [`numpy.typing` package](https://github.com/numpy/numpy/blob/main/numpy/_typing/_array_like.py). + +### Challenge 3: transformations and decorators + +JAX's Python API relies heavily on function transformations ({func}`~jax.jit`, {func}`~jax.vmap`, {func}`~jax.grad`, etc.), and this type of API poses a particular challenge for static type analysis. +Flexible annotation for decorators has been a [long-standing issue](https://github.com/python/mypy/issues/1927) in the mypy package, which was only recently resolved by the introduction of `ParamSpec`, discussed in [PEP 612](https://peps.python.org/pep-0612/) and added in Python 3.10. +Because JAX follows [NEP 29](https://numpy.org/neps/nep-0029-deprecation_policy.html), it cannot rely on Python 3.10 features until sometime after mid-2024. +In the meantime, Protocols can be used as a partial solution to this (JAX added this for jit and other methods in {jax-issue}`#9950`) and ParamSpec is possible to use via the `typing_extensions` package (a prototype is in {jax-issue}`#9999`) though this currently reveals fundamental bugs in mypy (see [python/mypy#12593](https://github.com/python/mypy/issues/12593)). +All that to say: it's not yet clear that the API of JAX's function transforms can be suitably annotated within the current constraints of Python type annotation tools. + +### Challenge 4: array annotation lack of granularity + +Another challenge here is common to all array-oriented APIs in Python, and has been part of the JAX discussion for several years (see {jax-issue}`#943`). +Type annotations have to do with the Python class or type of an object, whereas in array-based languages often the attributes of the class are more important. +In the case of NumPy, JAX, and similar packages, often we would wish to annotate particular array shapes and data types. + +For example, the arguments to the `jnp.linspace` function must be scalar values, but in JAX scalars are represented by zero-dimensional arrays. +So in order for annotations to not raise false positives, we must allow these arguments to be *arbitrary* arrays. +Another example is the second argument to `jax.random.choice`, which must have `dtype=int` when `shape=()`. +Python has a plan to enable type annotations with this level of granularity via Variadic Type Generics (see [PEP 646](https://peps.python.org/pep-0646/), slated for Python 3.11) but like `ParamSpec`, support for this feature will take a while to stabilize. + +There are some third-party projects that may help in the meantime, in particular [google/jaxtyping](https://github.com/google/jaxtyping), but this uses non-standard annotations and may not be suitable for annotating the core JAX library itself. +All told, the array-type-granularity challenge is less of an issue than the other challenges, because the main effect is that array-like annotations will be less specific than they otherwise could be. + +### Challenge 5: imprecise APIs inherited from NumPy + +A large part of JAX’s user-facing API is inherited from NumPy within the {mod}`jax.numpy` submodule. +NumPy’s API was developed years before static type checking became part of the Python language, and follows Python’s historic recommendations to use a [duck-typing](https://docs.python.org/3/glossary.html#term-duck-typing)/[EAFP](https://docs.python.org/3/glossary.html#term-eafp) coding style, in which strict type-checking at runtime is discouraged. As a concrete example of this, consider the {func}`numpy.tile` function, which is defined like this: + +```python +def tile(A, reps): + try: + tup = tuple(reps) + except TypeError: + tup = (reps,) + d = len(tup) + ... +``` + +Here the *intent* is that `reps` would contain either an `int` or a sequence of `int` values, but the *implementation* allows `tup` to be any iterable. +When adding annotations to this kind of duck-typed code, we could take one of two routes: +1. We may choose to annotate the *intent* of the function's API, which here might be something like `reps: Union[int, Sequence[int]]`. +2. Conversely, we may choose to annotate the *implementation* of the function, which here might look something like `reps: Union[ConvertibleToInt, Iterable[ConvertibleToInt]]` where `ConvertibleToInt` is a special protocol that covers the exact mechanism by which our function converts the inputs to integers (i.e. via `__int__`, via `__index__`, via `__array__`, etc.). Note also here that in a strict sense, `Iterable` is not sufficient here because there are objects in Python that duck-type as iterables but do not satisfy a static type check against `Iterable` (namely, an object that is iterable via `__getitem__` rather than `__iter__`.) + +The advantage of #1, annotating intent, is that the annotations are more useful to the user in communicating the API contract; while for the developer the flexibility leaves room for refactoring when necessary. The down-side (particularly for gradually-typed APIs like JAX's) is that it's quite likely that user code exists which runs correctly, but would be flagged as incorrect by a type checker. +Gradual typing of an existing duck-typed API means that the current annotation is implicitly `Any`, so changing this to a stricter type may present to users as a breaking change. + +Broadly speaking, annotating intent better serves Level 1 type checking, while annotating implementation better serves Level 3, while Level 2 is more of a mixed bag (both intent and implementation are important when it comes to annotations in IDEs). + +## JAX type annotation roadmap + +With this framing (Level 1/2/3) and JAX-specific challenges in mind, we can begin to develop our roadmap for implementing consistent type annotations across the JAX project. + +### Guiding Principles + +For JAX type annotation, we will be guided by the following principles: + +#### Purpose of type annotations + +We would like to support full, *Level 1, 2, and 3* type annotation as far as possible. In particular, this means that we should have restrictive type annotations on both inputs and outputs to public API functions. + +#### Annotate for intent + +JAX type annotations should in general indicate the **intent** of APIs, rather than the implementation, so that the annotations become useful to communicate the contract of the API. This means that at times inputs that are valid at runtime may not be recognized as valid by the static type checker (one example might be an arbitrary iterator passed in place of a shape that is annotated as `Shape = Sequence[int]`). + +#### Inputs should be permissively-typed + +Inputs to JAX functions and methods should be typed as permissively as is reasonable: for example, while shapes are typically tuples, functions that accept a shape should accept arbitrary sequences. Similarly, functions that accept a dtype need not require an instance of class `np.dtype`, but rather any dtype-convertible object. This might include strings, built-in scalar types, or scalar object constructors such as `np.float64` and `jnp.float64`. In order to make this as uniform as possible across the package, we will add a {mod}`jax.typing` module with common type specifications, starting with broad categories such as: + +- `ArrayLike` would be a union of anything that can be implicitly converted into an array: for example, jax arrays, numpy arrays, JAX tracers, and python or numpy scalars +- `DtypeLike` would be a union of anything that can be implicitly converted into a dtype: for example, numpy dtypes, numpy dtype objects, jax dtype objects, strings, and built-in types. +- `ShapeLike` would be a union of anything that could be converted into a shape: for example, sequences of integer or integer-like objecs. +- etc. + +Note that these will in general be simpler than the equivalent protocols used in {mod}`numpy.typing`. For example, in the case of `DtypeLike`, JAX does not support structured dtypes, so JAX can use a simpler implementation. Similarly, in `ArrayLike`, JAX generally does not support list or tuple inputs in place of arrays, so the type definition will be simpler than the NumPy analog. + +#### Outputs should be strictly-typed + +Conversely, outputs of functions and methods should be typed as strictly as possible: for example, for a JAX function that returns an array, the output should be annotated with something similar to `jnp.ndarray` rather than `ArrayLike`. Functions returning a dtype should always be annotated `np.dtype`, and functions returning a shape should always be `Tuple[int]` or a strictly-typed NamedShape equivalent. For this purpose, we will implement in {mod}`jax.typing` several strictly-typed analogs of the permissive types mentioned above, namely: + +- `Array` or `NDArray` (see below) for type annotation purposes is effectively equivalent to `Union[Tracer, jnp.ndarray]` and should be used to annotate array outputs. +- `DType` is an alias of `np.dtype`, perhaps with the ability to also represent key types and other generalizations used within JAX. +- `Shape` is essentially `Tuple[int, ...]`, perhaps with some additional flexibilty to account for dynamic shapes. +- `NamedShape` is an extension of `Shape` that allows for named shapes as used internall in JAX. +- etc. + +We will also explore whether the current implementation of `jax.numpy.ndarray` can be dropped in favor of making `ndarray` an alias of `Array` or similar. + +#### Err toward simplicity + +Aside from common typing protocols gathered in `jax.typing`, we should err on the side of simplicity. We should avoid constructing overly-complex protocols for arguments passed to API functions, and instead use simple unions such as `Union[simple_type, Any]` in the case that the full type specification of the API cannot be succinctly specified. This is a compromise that achieves the goals of Level 1 and 2 annotations, while punting on Level 3 in favor of avoiding unnecessary complexity. + +#### Avoid unstable typing mechanisms + +In order to not add undue development friction (due to the internal/external CI differences), we would like to be conservative in the type annotation constructs we use: in particular, when it comes to recently-introduced mechanisms such as `ParamSpec` ([PEP 612](https://peps.python.org/pep-0612/)) and Variadic Type Generics ([PEP 646](https://peps.python.org/pep-0646/)), we would like to wait until support in mypy and other tools matures and stabilizes before relying on them. + +One impact of this is that for the time being, when functions are decorated by JAX transformations like `jit`, `vmap`, `grad`, etc. JAX will effectively **strip all annotations** from the decorated function. +While this is unfortunate, at the time of this writing mypy has a laundry-list of incompatibilities with the potential solution offered by `ParamSpec` (see [`ParamSpec` mypy bug tracker](https://github.com/python/mypy/issues?q=is%3Aissue+is%3Aopen++label%3Atopic-paramspec+)), and we therefore judge it as not ready for full adoption in JAX at this time. +We will revisit this question in the future once support for such features stabilizes. + +Similarly, for the time being we will avoid adding the more complex & granular array annotations offered by the [jaxtyping](http://github.com/google/jaxtyping) project. This is a decision we could revisit at a future date. + +### `Array` Type Design Considerations + +As mentioned above, type annotation of arrays in JAX poses a unique challenge because of JAX's extensive use of duck-typing, i.e. passing and returning `Tracer` objects in place actual arrays within jax transformations. +This becomes increasingly confusing because objects used for type annotation often overlap with objects used for runtime instance checking, and may or may not correspond to the actual type hierarchy of the objects in question. +For JAX, we need to provide duck-typed objects for use in two contexts: **static type annotations** and **runtime instance checks**. + +The following discussion will assume that `jax.Array` is the runtime type on-device arrays, which is not yet the case but will be once the work in {jax-issue}`#12016` is complete. + +#### Static type annotations +We need to provide an object that can be used for duck-typed type annotations. +Assuming for the moment that we call this object `ArrayAnnotation`, we need a solution which satisfies `mypy` and `pytype` for a case like the following: +```python +@jit +def f(x: ArrayAnnotation) -> ArrayAnnotation: + assert isinstance(x, core.Tracer) + return x +``` +This could be accomplished via a number of approaches, for example: + +- Use a type union: `ArrayAnnotation = Union[Array, Tracer]` +- Create an interface file that declares `Tracer` and `Array` should be treated as subclasses of `ArrayAnnotation`. +- Restructure `Array` and `Tracer` so that `ArrayAnnotation` is a true base class of both. + +#### Runtime instance checks +We also must provide an object that can be used for duck-typed runtime `isinstance` checks. +Assuming for the moment that we call this object `ArrayInstance`, we need a solution that passes the following runtime check: +```python +def f(x): + return isinstance(x, ArrayInstance) +x = jnp.array([1, 2, 3]) +assert f(x) # x will be an array +assert jit(f)(x) # x will be a tracer +``` +Again, there are a couple mechanisms that could be used for this: + +- override `type(ArrayInstance).__instancecheck__` to return `True` for both `Array` and `Tracer` objects; this is how `jnp.ndarray` is currently implemented ([source](https://github.com/google/jax/blob/jax-v0.3.17/jax/_src/numpy/ndarray.py#L24-L49)). +- define `ArrayInstance` as an abstract base class and dynamically register it to `Array` and `Tracer` +- restructure `Array` and `Tracer` so that `ArrayInstance` is a true base class of both `Array` and `Tracer` + +A decision we need to make is whether `ArrayAnnotation` and `ArrayInstance` should be the same or different objects. There is some precedent here; for example in the core Python language spec, `typing.Dict` and `typing.List` exist for the sake of annotation, while the built-in `dict` and `list` serve the purposes of instance checks. +However, `Dict` and `List` are [deprecated](https://peps.python.org/pep-0585/#implementation) in newer Python versions in favor of using `dict` and `list` for both annotation and instance checks. + +#### Following NumPy's lead + +In NumPy's case, `np.typing.NDArray` serves the purpose of type annotations, while `np.ndarray` serves the purpose of instance checks (as well as array type identity). +Given this, it may be reasonable to conform to NumPy's precedent and implement the following: + +- `jax.Array` is the actual type of on-device arrays. +- `jax.typing.NDArray` is the object used for duck-typed array annotations. +- `jax.numpy.ndarray` is the object used for duck-typed array instance checks. + +This might feel somewhat natural to NumPy power-users, however this trifurcation would likely be a source of confusion: the choice of which to use for instance checks and annotations is not immediately clear. + +#### Unifying instance checks and annotation + +Another approach would be to unify type checking and annotation via override mechanisms mentioned above. + +##### Option 1: Partial unification +A partial unification might look like this: + +- `jax.Array` is the actual type of on-device arrays. +- `jax.typing.Array` is the object used for duck-typed array annotations (via `.pyi` interfaces on `Array` and `Tracer`). +- `jax.typing.Array` is also the object used duck-typed instance checks (via an `__isinstance__` override in its metaclass) + +In this approach, `jax.numpy.ndarray` would become a simple alias `jax.typing.Array` for backward compatibility. + + +##### Option 2: Full unification via overrides +Alternatively, we could opt for full unification via overrides: + +- `jax.Array` is the actual type of on-device arrays. +- `jax.Array` is also the object used for duck-typed array annotations (via a `.pyi` interface on `Tracer`) +- `jax.Array` is also the object used for duck-typed instance checks (via an `__isinstance__` override in its metaclass) + +Here, `jax.numpy.ndarray` would become a simple alias `jax.Array` for backward compatibility. + +##### Option 3: Full unification via class hierarchy +Finally, we could opt for full unification via restructuring of the class hierarchy and replacing duck-typing with OOP object hierarchies: + +- `jax.Array` is the actual type of on-device arrays +- `jax.Array` is also the object used for array annotations, by ensuring that `Tracer` inherits from `jax.Array` +- `jax.Array` is also the object used for instance checks, via the same mechanism + +Here `jnp.ndarray` could be an alias for `jax.Array`. +This final approach is in some senses the most pure, but it is somewhat forced from an OOP design standpoint (`Tracer` *is an* `Array`?). + +##### Option 4: Parial unification via class hierarchy +We could appease OOP pedants by instead making `Tracer` and `Array` derive from a common `ArrayBase` base class: + +- `jax.Array` is the actual type of on-device arrays +- `ArrayBase` is the object used for array annotations +- `ArrayBase` is also the object used for instance checks + +Here `jnp.ndarray` would be an alias for `ArrayBase`. +This may be purer from an OOP perspective, but it reintroduces a bifurcation and the distinction between `Array` and `ArrayBase` for annotation and instance checks may become confusing. + +##### Evaluation + +Considering the overall strengths and weaknesses of each potential approach: + +- From a user perspective, the unified approaches (options 2 and 3) are arguably best, because they remove the cognitive overhead involved in remembering which objects to use for instance checks or annotations: `jax.Array` is all you need to know +- Between Option 2 and Option 3, the purer (in an OOP sense) apporach is arguably Option 3 (`Tracer` as a subclass of `Array`), but in other senses it breaks the inheritance model, because it would require `Tracer` objects to carry all the baggage of `Array` objects (data buffers, sharding, devices, etc.) +- Option 2 is less pure in an OOP sense, but it aligns more closely with the spirit of how JAX is designed, with `Tracer` objects duck-typing as `Arrays`, and using mechanisms that Python provides to support this kind of duck typing. There is one minor technical hurdle involved; that is that `jax.Array` will be defined in C++ via pybind11, and pybind11 currently [does not support](https://github.com/pybind/pybind11/issues/2696) custom metaclasses required for overriding `__instancecheck__`; but it is likely possible to work around this via Python's C API. + +With this in mind, we conclude that Option 2 presents the best path forward. + +### Implementation Plan + +To move forward with type annotations, we will do the following: + +1. Iterate on this JEP doc until developers and stakeholders are bought-in. + +2. Create a private `jax._src.typing` (not providing any public APIs for now) and put in it the first level of simple types mentioned above: + + - Alias `Array = Any` for the time being, as this will take a bit more thought. + - `ArrayLike`: a Union of types valid as inputs to normal `jax.numpy` functions + - `Dtype` / `DtypeLike` (Check on capitalization of the `t`: what do other projects use?) + - `Shape` / `NamedShape` / `ShapeLike` + + The beginnings of this are done in {jax-issue}`#12300`. + +3. Begin work on `jax._src.typing.Array` that follows Option 2 from the previous section. + Because `jax.Array` is not yet out of experimental, we'll start by define `jax._src.typing.Array` with the type annotation and instance-checking features that will eventually move to `jax.Array` when it has fully landed. + +4. When this is implemented, remove the existing definition of `jnp.ndarray` and set is as an alias of `jax._src.typing.Array`. + +5. As a test, use these new typing definitions to comprehensively annotate functions within `jax.lax` according to the guidelines above. + +6. Continue adding additional annotations one module at a time, focusing on public API functions. + +7. Once `jax.Array` has fully landed, migrate the features of `jax._src.typing.Array` to this class, and let `jax.numpy.ndarray = Array`. + +8. When all is finalized, create a public `jax.typing` module that makes the above types available to users, along with documentation of annotation best practices for code using JAX. + +We will track this work in {jax-issue}`#12049`, from which this JEP gets its number. diff --git a/docs/jep/index.rst b/docs/jep/index.rst index 8f14dfa05..417d2dbf5 100644 --- a/docs/jep/index.rst +++ b/docs/jep/index.rst @@ -45,6 +45,7 @@ Then create a pull request that adds a file named 9419: Jax and Jaxlib versioning <9419-jax-versioning> 10657: Sequencing side-effects in JAX <10657-sequencing-effects> 11830: `jax.remat` / `jax.checkpoint` new implementation <11830-new-remat-checkpoint> + 12049: Type Annotation Roadmap for JAX <12049-type-annotations> Several early JEPs were converted in hindsight from other documentation,