Skip to content

[WIP] [DRAFT] Use *Map(... for T in Iter[...]) instead of *[...]#115

Draft
msullivan wants to merge 4 commits into
mainfrom
map
Draft

[WIP] [DRAFT] Use *Map(... for T in Iter[...]) instead of *[...]#115
msullivan wants to merge 4 commits into
mainfrom
map

Conversation

@msullivan
Copy link
Copy Markdown
Collaborator

This lets us turn maps over Iter[Any] into Any, which I think we
really might want to do.

…ation

Map is a new operator that wraps the comprehension and will eventually
support different semantics (e.g. propagating Any). For now it behaves
identically to the bare list comprehension. Mypy tests for files that
use Map are xfailed until the stubs are updated.
Iter[Any] now returns a generator that raises IterAnyError on first
__next__ (not __iter__, which CPython calls eagerly when constructing a
genexpr -- before Map can wrap it). Map catches IterAnyError via
`yield from` and yields a _UnpackAny sentinel, which callers of
_eval_args short-circuit into typing.Any.
Map.__iter__ now just yields an (_UnpackedMap, _UnpackedMapEnd) pair; the
evaluator drives the wrapped generator inside _eval_args, catches
IterAnyError there, and emits _UnpackAny. The trailing sentinel keeps
Union[*Map(...)] from collapsing to a single arg before _eval_union can
see it. IterAnyError is now a TypeMapError subclass, living entirely
inside the evaluator.

_eval_args iterates the generator one value at a time so that nested
genexprs closing over the outer iteration variable don't all see its
final value. _eval_union now routes through _eval_args too.
Both the _UnpackedMap branch and the plain-arg branch did the same
eval-and-unpack dance; pull it out.
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
python-typemap Ready Ready Preview, Comment Apr 17, 2026 4:45am

@jonathanslenders
Copy link
Copy Markdown

Question: why exactly is this better? Does Map() allow for better runtime evalution?

Related, I think it would be nice if we could write ... for ... in T when T is a variadic type parameter instead of ... for ... in Iter[tuple[*T]]

As an example, I had the following decorator:

def computed[*T, V](
    *reactives: *[signal[t] for t in Iter[tuple[*T]]],
    name: str | None = None,
) -> Callable[[Callable[[*T], V]], signal[V]]: ...

@msullivan
Copy link
Copy Markdown
Collaborator Author

Question: why exactly is this better? Does Map() allow for better runtime evalution?

Yes, the idea here is that we would like to be able to turn something like tuple[*[T | None for T in Iter[Any]]] into Any. This isn't doable with our current evaluation system because the list comprehension gets evaluated eagerly and the Iter needs to resolve to something.

By using Map and a generator comprehension, the computation is delayed and we can defer its execution to a place where we can catch an exception raised by it, allowing us to detect that case.

That said, I don't think we are going to go with this approach.
I think the real plan is going to be to require being able to extract the AST/string of annotations and support an AST based evaluation.
My plan there is to write another PEP for 3.16 for that. Backporting/prototyping gets annoying though; using from __future__ import annotations will be able to work, and I also have a zany scheme for doing "abstract interpretation" of the bytecode of __annotate__ functions to extract ASTs. (A mega-slop draft of that idea here: https://github.com/vercel/python-typemap/tree/bytecode-nonsense)

Related, I think it would be nice if we could write ... for ... in T when T is a variadic type parameter instead of ... for ... in Iter[tuple[*T]]

As an example, I had the following decorator:

def computed[*T, V](
    *reactives: *[signal[t] for t in Iter[tuple[*T]]],
    name: str | None = None,
) -> Callable[[Callable[[*T], V]], signal[V]]: ...

I will ponder this; it might be doable. Did that *reactives type work?

@jonathanslenders
Copy link
Copy Markdown

Thank you!

AST evaluation sounds nice.

The *reactives does not work. Mypy is crashing. You can try the following code if you'd like to reproduce:

from collections.abc import Callable
from dataclasses import dataclass

from typemap_extensions import Iter


@dataclass
class signal[T]:
    value: T | None


def computed[*T, V](
    *reactives: *[signal[t] for t in Iter[tuple[*T]]],
) -> Callable[[Callable[[*T], V]], signal[V]]:
    result = signal[V](None)

    def decorator(func: Callable[[*T], V]) -> signal[V]:
        # Not relevant right now. In here, we'd automatically update the result
        # signal, based on the input signals.
        return result

    return decorator


r1 = signal[int](1)
r2 = signal[int](2)


@computed(r1, r2)
def f1(value1: int, value2: int) -> int:
    # produce a new signal that's the sum of the given signals.
    return value1 + value2


@computed(r1, r2)
def f2(value1: str, value2: str) -> str:
    # Mypy should fail here! Signals are of type int, not str.
    return value1 + value2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants