Repository navigation
Integer generics #3345
Description
Dependent types have been brought up before in a few places: #366, this python-ideas thread, and most recently, #3062. The latter limits itself to one specific feature of a dependent type system, namely, support for simple literal types.
Another limited feature which could prove helpful is support for integer generics —that is, the ability to parametrize types by integers as well as other types. For example, the length of a homogeneous tuple could be parametrized:
# Currently supported:
Tuple[int, int, int] # A tuple of 3 ints
Tuple[int, ...] # A tuple of any number of ints
# Proposed:
Tuple[int, 256] # A tuple of 256 intsOne real-world use-case for this came up in python/typing#221:
# Currently supported:
SboxType = Tuple[
Tuple[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int],
Tuple[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int],
Tuple[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int],
Tuple[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int],
Tuple[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int],
Tuple[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int],
Tuple[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int],
Tuple[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int],
]
# Proposed:
SboxType = Tuple[Tuple[int, 16], 8] # A tuple of 8 tuples of 16 ints eachInteger genericity could also be used for things like statically checking vector or matrix dimensions:
N = TypeVar('N', numeric=True) # one idea for denoting type-level integers
class Vector(Generic[N]):
def __init__(self, values: Iterable[float, N]) -> None:
self.value = tuple(values)
def __add__(self, other: Vector[N]) -> Vector[N]:
return Vector(a + b for a, b in zip(self.values, other.values))
# ...more code...
def matmul(a: Matrix[N, M], b: Matrix[M, D]) -> Matrix[N, D]: ...Note that the Rust community has been discussing this for some time (as well as the more general const generics) and has a lot of material collected on the motivation, challenges, and details of such a feature:
- Pre-RFC: Integer Templating
- Parameterize types over numerics / type level integers rust-lang/rfcs#1038: Parameterize types over numerics / type level integers
- RFC: Constant generics (restricted Π types) for Rust, core RFC (v2) rust-lang/rfcs#1931: RFC: Constant generics (restricted Π types) for Rust, core RFC (v2)
- [lang-team-minutes] Const generics
Whether or not it's a good fit for Python is an open question, of course, but I figured I should at least bring it up for discussion! (Especially since it could interact with #3062 in interesting ways.)
(Even nicer would be to support simple type-level expressions so we could do things like this...
# Vector concatenation (relevant: http://stackoverflow.com/q/43292197/1974654)
def concat(a: Vector[N], b: Vector[M]) -> Vector[N+M]: ...
# The `zip` builtin (based on the typeshed stub)
@overload
def zip(iter1: Iterable[_T1, N1],
iter2: Iterable[_T2, N2]) -> Iterator[Tuple[_T1, _T2], min(N1, N2)]: ...
# HDL-like bit vector operations
def add(a: BitVec[N], b: BitVec[M]) -> BitVec[max(N, M) + 1]: ......but that may be taking it too far. To avoid running arbitrary user code the expression language would have to be explicitly defined and simple enough to implement in languages other than Python; it may be feasible to support something like +, -, *, //, %, **, max(), min(), and abs(), though.)
Activity
ilevkivskyi commentedon May 10, 2017
Thank you for interesting proposal and for research! I really like the idea of supporting fixed size arrays/tuples/matrices. I have seen many people already playing with combining mypy and numpy, but in my experience many errors in numpy are array shape mismatches, something like:
or
The point is that in most cases the array shapes/sizes are known statically, so that all these errors can be caught by simple integer dependent types.
JukkaL commentedon May 10, 2017
Tuples could easily be special cased in mypy so they aren't enough justification by themselves for this feature. I agree with @ilevkivskyi that numpy could be an interesting use case. The mypy core team currently is not planning to work on numpy support, however -- though I understand that many potential users could find it useful.
ilevkivskyi commentedon May 10, 2017
A possible strategy would be so start from a relatively small project (if/when someone will have time for this). For example have some special support for
numpy.ndarray(or maybe have a specialmypy_extensions.ndarraytype). We don't need to support user defined integer dependent types. (Similar to how we have variadic typesCalableandTuple, but we still don't allow user defined variadic generics.)PaperclipBadger commentedon Aug 29, 2017
As for
numpyso fortensorflow. It'd be disheartening to find out that special support fornumpy.ndarrayallowed you to catch shape-mismatch errors at compile time, but that support was not available fortensorflow.Tensor, which has near-identical semantics.saulshanabrook commentedon Jul 2, 2018
One way to think about multidimensional arrays are as a function from indices to a value. The "shape" really just constrains the possible inputs to the functions. I am playing with implementing the Mathematics of Arrays in Python and I can represent an array object as a tuple of an indexing function and a shape tuple. But this doesn't capture the relationship between these two parts at the type level.
If we also want to track the inner types in the arrays, this requires a generic array object. So at the type system, I want to be able to represent an array object of of type
Tand shapeSas "A callable with integer args <= S and output type T".saulshanabrook commentedon Jul 2, 2018
It looks like this could be accomplished with generic protocols, like this:
An alternative way of doing this is to just use my own type annotations that can't be typed statically and just use them at runtime. This is what the Relay project does, which is part of the TVM ML stack from U of Washington:
They just disable static type checking for those types.
This is a bit of a tangent, but I attended some of PLDI this year and there was a ton of talk there about the Python type system and language. Lots of people were building these computational frameworks in Python for probabilistic programming or deep learning and were coming from other languages, likes Haskell, Rust, or Java. I am not that familiar with the community there, but it seems like it would be good if some of that energy could be focused on research of Python's core so that these types of extensions to the language could be better supported (have reasonable error messages, static type checking, etc).
gvanrossum commentedon Jul 2, 2018
OT: please don't use images of text. There's no reason you couldn't have copy-pasted the original code.
saulshanabrook commentedon Jul 2, 2018
Duly noted. I updated the post with code instead of the image.
msegado commentedon Jul 3, 2018
Could you explain a bit more about how this would work? I'm afraid I don't see how generic protocols could handle shape/size constraints at the type level... out-of-bounds checking would still need to be handled at runtime in the
indexmethod, no? (In contrast, integer generics would allow something much closer to what the Relay project is doing.)leonardo-m commentedon Jul 30, 2018
For Rust the accepted RFC is:
rust-lang/rfcs#2000
And further improvements:
rust-lang/rust#76560
Currently a typing problem above could be handled like this:
graue70 commentedon May 5, 2020
Is a case like this considered as well?
PyCharm infers
Union[Iterator[Tuple[Any]], Iterator[Tuple[Any, Any]], Iterator[Tuple[Any, Any, Any]], Iterator[Tuple[Any, Any, Any, Any]], Iterator[Tuple[Any, Any, Any, Any, Any]]]as the return type (for some reason stopping at five elements).As it is now, I don't think I can do better than
Iterator[Tuple[str, ...]]which leaves out the length information.fylux commentedon Sep 11, 2020
Hi! I have been working on a proposal for adding support for type arithmetic and some other useful features for tensor typing. I will present it next Monday in the next chapter of the Tensor Typing meetings that we have been organising, so if anyone is interested feel free to attend: link
gvanrossum commentedon Sep 11, 2020
Looking forward to it!
leonardo-m commentedon Sep 11, 2020
See also:
https://futhark-lang.org/blog/2020-03-15-futhark-0.15.1-released.html
luphord commentedon Nov 25, 2020
@fylux Is there a write-up of your proposal available anywhere?
fylux commentedon Nov 25, 2020
@luphord The write-up is not ready yet but the slides of the presentation can give you a good idea: link
leonardo-m commentedon Nov 27, 2020
@luphord I hope eventually some of this stuff will also go to statically typed languages as Rust :-) Rust "const generics" aren't enough.
luphord commentedon Nov 27, 2020
@fylux Oh wow, that is a lot more advanced than what I expected, thanks!
I was merely thinking of simple use cases such as (in "numpy terminology")
and then mypy would recognize a type error in
However, I can clearly see the benefits of type arithmetic that you presented. Is there a road map for your proposal? I understand that at least parts of it are already implemented in pyre?
fylux commentedon Nov 27, 2020
Regarding support for "simple cases", that is already possible in Pyre thanks to variadics (e.g. XLM example), and there is currently an open proposal for standardising it in Python (https://mail.python.org/archives/list/[email protected]/thread/SQVTQYWIOI4TIO7NNBTFFWFMSMS2TA4J/).
About type arithmetic, +, -, //, *, Length and Product are all already supported in Pyre (e.g MaxPool2D example).
The final proposal for type arithmetic is not yet written, mostly because type arithmetic will highly benefit from having support for variadics, so until the variadic PEP is merged there is not so much sense on discussion a type arithmetic PEP.
shaunc commentedon Apr 3, 2021
Could a integer generic be considered a generic over literals? E.g.
It would seem that allowing a generic to be bound to a literal of a type almost avoids introducing new concepts. Everywhere it could be treated as a literal whose exact value wasn't available. Features like "unrolling" tuples, arithmetic, etc, could be implemented in smaller chunks on top of this.
fylux commentedon Apr 5, 2021
Regarding the concept of using generics over literal integers as integer generics it is something that we explored in Pyre, but we faced several problems when dealing with values that are unknown. You could always restrict yourself to values that are known at compile time, but the goal is that known and unknown values can interact in a sound way.
If for example, you use integer generics for dealing with vectors, you will often not know the size of the vector at compile time. Let's say that you have a function to read a CSV where you know the number of columns but not the number of rows, how would you express that unknown number of rows?
Or similarly, let's say that you want to write the type of a variable calling this function.
In this example, the "?" must be a literal int according to the previous definition of generic over literals. The number of rows is an exact number, but we don´t know which. Thus, we could say that ? is the union of all possible literal integers, something like "AnyInt", "Literal[int]", etc, but then we would be introducing a new concept in the type system.
If Python could track unbound type variables and resonate on them, then you could write something like:
Based on my experience, I am currently leaning towards relying on the existing definition of
intfor dealing with integer generics. If we define N as a subtype of int, then it can be replaced by any literal or also by int itself, which in this case would be used to represent that the number is not known at compile time.Actually, Pyre uses as a reference this concept of integer generics for type arithmetic, so that "int" represents unknown information and arithmetic on "int" propagates "int".
Finally, @shaunc, I personally agree with the following "It would seem that allowing a generic to be bound to a literal of a type almost avoids introducing new concepts". Currently, I don´t see a good mechanism for setting bounds or subtypes on specific types of literals (not just ints) so I think that it is something that must be explored.
gvanrossum commentedon Apr 5, 2021
To me this looks like the best solution:
shaunc commentedon Apr 5, 2021
Generics bounded by literals of types also offers a way to introduce default values and the like:
WRT arithmetic: this issue (and others similar) seem to have languished, perhaps because they are heavy lifts. I was hoping "generics bounded by literals" would suggest a path forward that would allow functionality to be added incrementally, while still providing a basis for natural extension to more powerful constructs.
samtygier-stfc commentedon May 13, 2025
Related discussion at python/typing#786
sneakers-the-rat commentedon Jun 9, 2026
i'm working on a mypy plugin that would strongly benefit from this, so i'm willing to put in the time to write a patch for mypy to at least do the basic thing that @fylux describes above (i.e. without attempting operators like addition):
Don't want to have this perceived as self-promo, so collapsing this, but giving example for concreteness of the syntax i'm after where a typevar is checked for equality within a class scope (or at least within the scope of the generated init method):
expand/collapse class-scoped example
this is for numpydantic where i recently added a mypy plugin that enriches the type annotations from numpy constructors so they have the literal shape of their args (when possible), and the
NDArraytypes have a more general syntax than what numpy typing currently supportswhich would match mypy's current behavior of inferring the value of the generic from the constructor args (which would be a
Literal[10]rather thanint), and the current behavior of typevars being defined within the scope of class body.So what i would want to do is the simplest case of checking that generics within a given scope are equal. I'm somewhat surprised this isn't already the case, given the constraint solver docs, because even an example like this where T is invariant, which seems like it should fail, currently does not fail in 2.1.0 even with
--strictI imagine this would probably be breaking for a lot of existing code, so i would probably put it behind a
--strict-typevar-equalityflag.I just wanted to check in first before i started working on this that this would a) be acceptable, and b) there isn't someone already working on it (i would be down to help!), and c) if the discussion has moved in a different direction since then. Searched around and all the other related issues pointed here, and I think this is consistent with the typing spec, but my apologies if this is somehow expected behavior.