# Use \* operator to type fixed-size collections

**URL:** <https://discuss.python.org/t/use-operator-to-type-fixed-size-collections/38508>\
**Category:** Ideas\
**Tags:** typing\
**Created:** [November 12, 2023, 2:01pm UTC](https://discuss.python.org/t/use-operator-to-type-fixed-size-collections/38508 "2023-11-12T14:01:32Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![loic-simon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/loic-simon/32/37461_2.png) [@loic-simon](https://discuss.python.org/u/loic-simon)\
**Post date:** [November 12, 2023, 2:01pm UTC](https://discuss.python.org/t/use-operator-to-type-fixed-size-collections/38508/1 "2023-11-12T14:01:33Z")

</div>

Every now and then, I happen to work with fixed-size homogenous collections… and find no satisfying way to annotate them.

Let’s say I have a function to compute in-place a [Fibonacci generalized sequence](https://en.wikipedia.org/wiki/Generalizations_of_Fibonacci_numbers#Fibonacci_integer_sequences) depending on the first two terms:

```python
def compute_fibonacci_terms(seq, n_terms):
    a, b = seq
    for _ in range(n_terms):
        a, b = b, a + b
        seq.append(b)
    return seq

```

```python
>>> compute_fibonacci_terms([3, 4], n_terms=5)
[3, 4, 7, 11, 18, 29, 47]

```

To annotate this function, the two choices I see are

```python
def compute_fibonacci_terms(seq: list[int], n_terms: int) -> list[int]: ...

```

```python
def compute_fibonacci_terms(seq: tuple[int, int], n_terms: int) -> list[int]: ...

```

The first solution allows to pass list with any number of items, resulting in a `ValueError` in this (admittedly contrived) example. The second solution is even worse, disallowing passing lists (_which is why this function was designed that way_) and rightly reporting an error on `seq.append`.

* * *

Even when working with tuples, the situation is not ideal when it comes to large tuples:

```python
def get_weekday_names(locale: str) -> tuple[str, str, str, str, str, str, str]:
    ...

```

Here type-checking works well, but is quite cumbersome to read and error-prone.

## Proposal

What I’d like to propose here is to **overload the ` __mul__ ` operator of `type` to denote fixed size homogenous collections**.

So the above functions could be written as

```python
def compute_fibonacci_terms(seq: list[int * 2], n_terms: int) -> list[int]:
    ...

```

```python
def get_weekday_names(locale: str) -> tuple[str * 7]:
    ...

```

At runtime, `type` (_as well as of some typing special forms usable as type annotations_) would define ` __mul__ ` to ony accept an integer and return some special typing construct, similar to `typing.GenericAlias`. Type checkers would only allow literal integers.

## Considerations

I believe type checkers should only allow this syntax inside generic types derived of `Collection` \[1\].  
Type-checking wise, `tuple[int, int, int]` and `tuple[int * 3]` should be considered fully equivalent.

Since `type` and typing special constructs cannot currently be multiplied by anything, this should be fully backwards compatible. One case that could be problematic are defered anotations, since `list["LateBind" * 3]` would become `list["LateBindLateBindLateBind"]` 😅, but I believe type checkers can just flag this as invalid (_and suggest using `list["LateBind * 3"]` instead_).

> **Future improvements this may open the way for...**
>
> - Typing of heterogenous sequences (`list[int * 3, str, int * 2]`), even it I find that less readable and probably a way less common use case;
> - Bounded iterable size: for example, [`itertools.tee`](https://docs.python.org/3/library/itertools.html#itertools.tee) could have an overload
> 
> ```python
> def tee[I, N: Literal[int]](iterable: Iterable[I], n: N=2) -> tuple[I * N]: ...
> 
> ```
> 
> or even, to get back to my example,
> 
> ```python
> compute_fibonacci_terms[N: Literal[int]](seq: list[int * 2], n_terms: N) -> list[int * (N + 2)]: ...
> 
> ```
> 
> - Some way to denote “at least X” / “at most X” items ?

* * *

Has something in these lines been discussed before? I’m pretty sure I read a discussion about this issue somewhere here, but I couldn’t find it 😔 and I don’t think it really had a proposal.

_NB: I hesited between #Typing and #Ideas for this post, I preferred the later since this has implications on runtime and does not rely on advanced typing notions, but feel free to move it!_

* * *

1. maybe `arg: int * 3` could be made sugar for `arg: Collection[int * 3]`, but I’m not sure I’m fan of the idea.

---

<div class="post-metadata">

**Author:** ![AlexWaygood](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/alexwaygood/32/4722_2.png) [@AlexWaygood](https://discuss.python.org/u/AlexWaygood)\
**Post date:** [November 12, 2023, 2:12pm UTC](https://discuss.python.org/t/use-operator-to-type-fixed-size-collections/38508/2 "2023-11-12T14:12:21Z")

</div>

> [@loic-simon](#):
>
> Has something in these lines been discussed before?

There was some previous discussion on this problem at [Feature Request: Type hint for Fixed Length Homogeneous Sequences · Issue #786 · python/typing · GitHub](https://github.com/python/typing/issues/786)

---

<div class="post-metadata">

**Author:** ![Tinche](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tinche/32/12507_2.png) [@Tinche](https://discuss.python.org/u/Tinche)\
**Post date:** [November 12, 2023, 2:39pm UTC](https://discuss.python.org/t/use-operator-to-type-fixed-size-collections/38508/3 "2023-11-12T14:39:13Z")

</div>

This is an interesting idea, but…

> [@loic-simon](#):
>
> The second solution is even worse, disallowing passing lists

I don’t think lists could ever work in this context. First, I think it’d be extremely difficult for a typechecker to know how many elements might be in a given list; these are basically dependent types, right? That’s a very advanced typing concept.

Secondly, assume you have a class that has a field of type `list[int * 4]` and you happen to have a list of 4 ints, the checker knows it, and you pass it in. Then a few lines later you pop an element and now it’s a `list[int * 3]`, but the class instance still thinks it has 4 elements.

I think for this to be sound the type should be immutable, so tuples. Maybe `tuple[int * n]` (or `tuple[int; n]`) could just be a shorthand for a homogenous tuple of n elements?

---

<div class="post-metadata">

**Author:** ![loic-simon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/loic-simon/32/37461_2.png) [@loic-simon](https://discuss.python.org/u/loic-simon)\
**Post date:** [November 12, 2023, 2:56pm UTC](https://discuss.python.org/t/use-operator-to-type-fixed-size-collections/38508/4 "2023-11-12T14:56:30Z")

</div>

> [@Tinche](#):
>
> First, I think it’d be extremely difficult for a typechecker to know how many elements might be in a given list; these are basically dependent types, right? That’s a very advanced typing concept.

I see your point. Indeed, I did not considered the type checker work that would be needed outside of the “passing a literal list to a typed function”, but I suppose allowing `list[int * 5]` would not make sense within the current scope of type checkers.

I think this “syntax” may be useful even limited to frozen collections (aka tuples and frozensets?) though! This is more in line with the discussion linked by Alex above, indeed 😄

---

<div class="post-metadata">

**Author:** ![ajoino](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ajoino/32/6907_2.png) [@ajoino](https://discuss.python.org/u/ajoino)\
**Post date:** [November 12, 2023, 3:57pm UTC](https://discuss.python.org/t/use-operator-to-type-fixed-size-collections/38508/5 "2023-11-12T15:57:06Z")

</div>

This sounds akin to [design-by-contract](https://en.m.wikipedia.org/wiki/Design_by_contract) which I think is beyond the scope of the type system. However, there are some libraries on PyPI that implements this but I’m not sure if they do static analysis.

---

<div class="post-metadata">

**Author:** ![apalala](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/apalala/32/683_2.png) [@apalala](https://discuss.python.org/u/apalala)\
**Post date:** [November 12, 2023, 5:29pm UTC](https://discuss.python.org/t/use-operator-to-type-fixed-size-collections/38508/6 "2023-11-12T17:29:22Z")

</div>

I like the proposal. The implementation is simple, and type-checkers should detect the odd cases.

This is already detected as a typing error by mypy:

```python
y: list[int, str] = []

```

> error: “list” expects 1 type argument, but 2 given [type-arg]

---

<div class="post-metadata">

**Author:** ![flyinghyrax](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/flyinghyrax/32/12394_2.png) [@flyinghyrax](https://discuss.python.org/u/flyinghyrax)\
**Post date:** [November 13, 2023, 2:58am UTC](https://discuss.python.org/t/use-operator-to-type-fixed-size-collections/38508/7 "2023-11-13T02:58:12Z")

</div>

A neat idea to ease an ergonomic pain-point. I’d agree that it’s tedious to write `tuple[T, T, T, T]`. However I’d personally prefer no more “special cases” be added until more rigorous foundations are specified for the type system in general. The usage of the subscript syntax in annotations feels overloaded and inconsistent enough as it is.

---

<div class="post-metadata">

**Author:** ![loic-simon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/loic-simon/32/37461_2.png) [@loic-simon](https://discuss.python.org/u/loic-simon)\
**Post date:** [November 13, 2023, 7:31pm UTC](https://discuss.python.org/t/use-operator-to-type-fixed-size-collections/38508/8 "2023-11-13T19:31:36Z")

</div>

> [@flyinghyrax](#):
>
> However I’d personally prefer no more “special cases” be added until more rigorous foundations are specified for the type system in general.

Yeah, I totally understand that! That’s more or less the reason why I didn’t push my proposal about [extracting kwargs from function signatures](https://discuss.python.org/t/extract-kwargs-types-from-a-function-signature/34238) further. Hope this project and related debates will progress quickly and lead so exciting features!

Do you think special-casing `tuple[SomeType, Literal[int]]` could still be relevant? Or even `tuple[SomeType, TypeVar(bound=Literal[int])]`, which is AFAIK not doable today!

---

<div class="post-metadata">

**Author:** ![flyinghyrax](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/flyinghyrax/32/12394_2.png) [@flyinghyrax](https://discuss.python.org/u/flyinghyrax)\
**Post date:** [November 13, 2023, 8:24pm UTC](https://discuss.python.org/t/use-operator-to-type-fixed-size-collections/38508/9 "2023-11-13T20:24:59Z")

</div>

> [@loic-simon](#):
>
> Do you think special-casing `tuple[SomeType, Literal[int]]` could still be relevant? Or even `tuple[SomeType, TypeVar(bound=Literal[int])]`, which is AFAIK not doable today!

This is the part of the proposal that feels most consistent to me, at least as far as consistency with the existing `tuple` annotation syntax. For example:

1. `tuple[T, U, V]` - heterogenous tuple of known length
2. `tuple[T, …]` - homogenous tuple of _unknown_ length
3. `tuple[T, 5]` - homogenous tuple of _known_ length

(1) and (2) we already have; (3) seems like a reasonable extension of (2) and (I imagine) could be implemented as sugar for (1).
