From f1da49c68936f4bd5af1f92d2a092cf5676f0b2f Mon Sep 17 00:00:00 2001 From: Tyler Smart Date: Sat, 13 Jun 2026 08:00:36 -0700 Subject: [PATCH 1/7] Remove `from __future__ import annotations` --- src/iterum/_diterum.py | 2 -- src/iterum/_iterum.py | 2 -- src/iterum/_notset.py | 2 -- src/iterum/_option.py | 2 -- src/iterum/_ordering.py | 2 -- src/iterum/_seq.py | 2 -- src/iterum/_singleton.py | 2 -- src/iterum/_type_helpers.py | 2 -- tests/test_iterum.py | 2 -- tests/test_option.py | 2 -- type_tests/option_helpers.py | 2 -- type_tests/type_iter.py | 2 -- type_tests/type_nil.py | 2 -- type_tests/type_option.py | 2 -- type_tests/type_seq.py | 2 -- type_tests/type_some.py | 2 -- 16 files changed, 32 deletions(-) diff --git a/src/iterum/_diterum.py b/src/iterum/_diterum.py index 6008948..60b2173 100644 --- a/src/iterum/_diterum.py +++ b/src/iterum/_diterum.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from abc import abstractmethod from collections.abc import Callable, Sequence diff --git a/src/iterum/_iterum.py b/src/iterum/_iterum.py index 90d4255..424fdbc 100644 --- a/src/iterum/_iterum.py +++ b/src/iterum/_iterum.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import builtins import itertools from abc import abstractmethod diff --git a/src/iterum/_notset.py b/src/iterum/_notset.py index 02c21c2..ae62823 100644 --- a/src/iterum/_notset.py +++ b/src/iterum/_notset.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import TypeGuard from ._singleton import Singleton diff --git a/src/iterum/_option.py b/src/iterum/_option.py index 19a7e35..2f8e0e0 100644 --- a/src/iterum/_option.py +++ b/src/iterum/_option.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from collections.abc import Callable from typing import ( TYPE_CHECKING, diff --git a/src/iterum/_ordering.py b/src/iterum/_ordering.py index 42be869..f228403 100644 --- a/src/iterum/_ordering.py +++ b/src/iterum/_ordering.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from enum import Enum, unique from ._singleton import create_singleton diff --git a/src/iterum/_seq.py b/src/iterum/_seq.py index 85d9669..eaa5e34 100644 --- a/src/iterum/_seq.py +++ b/src/iterum/_seq.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import operator from types import EllipsisType from typing import Literal, SupportsIndex, overload diff --git a/src/iterum/_singleton.py b/src/iterum/_singleton.py index e23701d..47fadc0 100644 --- a/src/iterum/_singleton.py +++ b/src/iterum/_singleton.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Self diff --git a/src/iterum/_type_helpers.py b/src/iterum/_type_helpers.py index 399888a..6e55cab 100644 --- a/src/iterum/_type_helpers.py +++ b/src/iterum/_type_helpers.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import TYPE_CHECKING, Any, Protocol, TypeVar if TYPE_CHECKING: diff --git a/tests/test_iterum.py b/tests/test_iterum.py index 3cde6f1..db75dc2 100644 --- a/tests/test_iterum.py +++ b/tests/test_iterum.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from collections.abc import Iterator from functools import partial diff --git a/tests/test_option.py b/tests/test_option.py index 202172f..e7c3ab5 100644 --- a/tests/test_option.py +++ b/tests/test_option.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import pytest from iterum import ExpectNilError, Option, Some, UnwrapNilError, nil diff --git a/type_tests/option_helpers.py b/type_tests/option_helpers.py index ded5e15..d4848f5 100644 --- a/type_tests/option_helpers.py +++ b/type_tests/option_helpers.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from iterum import Nil, Option, Some diff --git a/type_tests/type_iter.py b/type_tests/type_iter.py index e418f58..275e5ea 100644 --- a/type_tests/type_iter.py +++ b/type_tests/type_iter.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from collections.abc import Iterable from typing import TypeVar, assert_type diff --git a/type_tests/type_nil.py b/type_tests/type_nil.py index 142bb73..c8b60f3 100644 --- a/type_tests/type_nil.py +++ b/type_tests/type_nil.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Literal, NoReturn, TypeVar, assert_type from iterum import Nil, Option, Some, Swap, iterum, nil diff --git a/type_tests/type_option.py b/type_tests/type_option.py index 42b08c9..09b9a27 100644 --- a/type_tests/type_option.py +++ b/type_tests/type_option.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Any, TypeVar, assert_type from iterum import Nil, Option, Some, Swap, iterum, nil diff --git a/type_tests/type_seq.py b/type_tests/type_seq.py index 13924bc..d2e4380 100644 --- a/type_tests/type_seq.py +++ b/type_tests/type_seq.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import assert_type from iterum import InfSeq, Seq, seq diff --git a/type_tests/type_some.py b/type_tests/type_some.py index 861e515..f141a5d 100644 --- a/type_tests/type_some.py +++ b/type_tests/type_some.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Literal, TypeVar, assert_type from iterum import Nil, Option, Some, Swap, iterum, nil From 27596e3e28ce105dab1a9642f6702dec77012088 Mon Sep 17 00:00:00 2001 From: Tyler Smart Date: Sat, 13 Jun 2026 16:38:08 -0700 Subject: [PATCH 2/7] Remove typevar from option.py --- src/iterum/_option.py | 57 ++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/src/iterum/_option.py b/src/iterum/_option.py index 2f8e0e0..494f5b6 100644 --- a/src/iterum/_option.py +++ b/src/iterum/_option.py @@ -5,7 +5,6 @@ Literal, NamedTuple, NoReturn, - TypeVar, overload, ) @@ -15,14 +14,6 @@ from ._iterum import iterum -T = TypeVar("T") -U = TypeVar("U") -V = TypeVar("V") - -S = TypeVar("S", bound="Some") -O = TypeVar("O", bound="Option") # noqa: E741 - - class Swap[T, U](NamedTuple): """ Used for various 'swapping' operations on [Option][iterum.Option]. @@ -93,7 +84,7 @@ def __repr__(self) -> str: def __bool__(self) -> Literal[False]: return False - def and_(self, optb: Option[U], /) -> Nil: + def and_[U](self, optb: Option[U], /) -> Nil: """ Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise returns optb. @@ -117,7 +108,7 @@ def and_(self, optb: Option[U], /) -> Nil: # 'and' is a keyword, so instead we use 'and_' return self - def and_then(self, f: Callable[[Any], Option[U]], /) -> Nil: + def and_then[U](self, f: Callable[[Any], Option[U]], /) -> Nil: """ Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise calls `f` with the wrapped value and returns the result. @@ -202,7 +193,7 @@ def flatten(self) -> Nil: """ return self - def get_or_insert(self, value: T, /) -> Swap[Some[T], T]: + def get_or_insert[T](self, value: T, /) -> Swap[Some[T], T]: """ Inserts value into the option if it is [nil][iterum.nil], then returns a tuple of the resulting option and the returned value. @@ -245,7 +236,7 @@ def get_or_insert(self, value: T, /) -> Swap[Some[T], T]: """ return Swap(Some(value), value) - def get_or_insert_with(self, f: Callable[[], T], /) -> Swap[Some[T], T]: + def get_or_insert_with[T](self, f: Callable[[], T], /) -> Swap[Some[T], T]: """ Inserts a value computed from `f` into the option if it is [nil][iterum.nil], then returns a tuple of the resulting option and the @@ -288,7 +279,7 @@ def get_or_insert_with(self, f: Callable[[], T], /) -> Swap[Some[T], T]: """ return Swap(Some(value := f()), value) - def insert(self, value: T, /) -> Swap[Some[T], T]: + def insert[T](self, value: T, /) -> Swap[Some[T], T]: """ Inserts value into the option, then returns a tuple of the resulting option and the returned value. @@ -411,7 +402,7 @@ def map(self, f: Callable[[Any], Any], /) -> Nil: """ return self - def map_or(self, default: U, f: Callable[[Any], U], /) -> U: + def map_or[U](self, default: U, f: Callable[[Any], U], /) -> U: """ Returns the provided default result (if [nil][iterum.nil]), or applies a function to the contained value (if any). @@ -430,7 +421,7 @@ def map_or(self, default: U, f: Callable[[Any], U], /) -> U: """ return default - def map_or_else(self, default: Callable[[], U], f: Callable[[Any], U], /) -> U: + def map_or_else[U](self, default: Callable[[], U], f: Callable[[Any], U], /) -> U: """ Computes a default function result (if [nil][iterum.nil]), or applies a different function to the contained value (if any). @@ -498,7 +489,7 @@ def ok_or_else(self, err: Callable[[], Exception], /) -> NoReturn: """ raise err() - def or_(self, optb: O, /) -> O: + def or_[O: Option](self, optb: O, /) -> O: """ Returns the option if it contains a value, otherwise returns optb. @@ -521,7 +512,7 @@ def or_(self, optb: O, /) -> O: # 'or' is a keyword, so instead we use 'or_' return optb - def or_else(self, f: Callable[[], O], /) -> O: + def or_else[O: Option](self, f: Callable[[], O], /) -> O: """ Returns the option if it contains a value, otherwise calls `f` and returns the result. @@ -543,7 +534,7 @@ def or_else(self, f: Callable[[], O], /) -> O: """ return f() - def replace(self, value: T, /) -> Swap[Some[T], Nil]: + def replace[T](self, value: T, /) -> Swap[Some[T], Nil]: """ Replaces the actual value in the option by the value given in parameter, returning a tuple of the resulting option and the returned old value if @@ -656,7 +647,7 @@ def unwrap(self) -> NoReturn: """ raise UnwrapNilError() - def unwrap_or(self, default: T, /) -> T: + def unwrap_or[T](self, default: T, /) -> T: """ Returns the contained [Some][iterum.Some] value or a provided default. @@ -685,7 +676,7 @@ def unwrap_or(self, default: T, /) -> T: # If I wanted to get real fancy could provide user way to register defaults # for their custom types. - def unwrap_or_else(self, f: Callable[[], T], /) -> T: + def unwrap_or_else[T](self, f: Callable[[], T], /) -> T: """ Returns the contained [Some][iterum.Some] value or computes it from a closure. @@ -718,12 +709,12 @@ def unzip(self) -> tuple[Nil, Nil]: return (nil, nil) @overload - def xor(self, optb: S, /) -> S: ... + def xor[S: Some](self, optb: S, /) -> S: ... @overload def xor(self, optb: Nil, /) -> Nil: ... - def xor(self, optb: O, /) -> O | Nil: + def xor[O: Option](self, optb: O, /) -> O | Nil: """ Returns [Some][iterum.Some] if exactly one of `self`, `optb` is [Some][iterum.Some], otherwise returns [nil][iterum.nil]. @@ -740,7 +731,7 @@ def xor(self, optb: O, /) -> O | Nil: """ return nil if isinstance(optb, Nil) else optb - def zip(self, other: Option[U], /) -> Nil: + def zip[U](self, other: Option[U], /) -> Nil: """ Zips `self` with another option. @@ -800,7 +791,7 @@ def __repr__(self) -> str: def __bool__(self) -> Literal[True]: return True - def and_(self, optb: O, /) -> O: + def and_[O: Option](self, optb: O, /) -> O: """Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise returns optb. @@ -822,7 +813,7 @@ def and_(self, optb: O, /) -> O: """ return optb - def and_then(self, f: Callable[[T], O], /) -> O: + def and_then[O: Option](self, f: Callable[[T], O], /) -> O: """Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise calls `f` with the wrapped value and returns the result. @@ -889,7 +880,7 @@ def filter(self, predicate: Callable[[T], object], /) -> Option[T]: """ return self if predicate(self._value) else Nil() - def flatten(self: Some[O]) -> O: + def flatten[O: Option](self: Some[O]) -> O: """Converts from `Option[Option[T]]` to `Option[T]`. **Examples:** @@ -1093,7 +1084,7 @@ def iter(self) -> iterum[T]: return iterum([self._value]) - def map(self, f: Callable[[T], U], /) -> Some[U]: + def map[U](self, f: Callable[[T], U], /) -> Some[U]: """Maps an [Option[T]][iterum.Option] to [Option[U]][iterum.Option] by applying a function to a contained value (if [Some][iterum.Some]) or returns [nil][iterum.nil] (if [Nil][iterum.Nil]). @@ -1108,7 +1099,7 @@ def map(self, f: Callable[[T], U], /) -> Some[U]: """ return Some(f(self._value)) - def map_or(self, default: U, f: Callable[[T], U], /) -> U: + def map_or[U](self, default: U, f: Callable[[T], U], /) -> U: """ Returns the provided default result (if [nil][iterum.nil]), or applies a function to the contained value (if any). @@ -1127,7 +1118,7 @@ def map_or(self, default: U, f: Callable[[T], U], /) -> U: """ return f(self._value) - def map_or_else(self, default: Callable[[], U], f: Callable[[T], U], /) -> U: + def map_or_else[U](self, default: Callable[[], U], f: Callable[[T], U], /) -> U: """ Computes a default function result (if [nil][iterum.nil]), or applies a different function to the contained value (if any). @@ -1383,7 +1374,7 @@ def unwrap_or_else(self, f: Callable[[], T], /) -> T: """ return self._value - def unzip(self: Some[tuple[U, V]]) -> tuple[Some[U], Some[V]]: + def unzip[U, V](self: Some[tuple[U, V]]) -> tuple[Some[U], Some[V]]: """Unzips an option containing a tuple of two options. If `self` is `Some((a, b))` this method returns `(Some(a), Some(b))`. @@ -1424,12 +1415,12 @@ def xor(self, optb: Option[T], /) -> Option[T]: return self if isinstance(optb, Nil) else nil @overload - def zip(self, other: Some[U], /) -> Some[tuple[T, U]]: ... + def zip[U](self, other: Some[U], /) -> Some[tuple[T, U]]: ... @overload def zip(self, other: Nil, /) -> Nil: ... - def zip(self, other: Option[U], /) -> Option[tuple[T, U]]: + def zip[U](self, other: Option[U], /) -> Option[tuple[T, U]]: """ Zips `self` with another option. From 1b744c4584ab189d1c007726350ca3c0b610a012 Mon Sep 17 00:00:00 2001 From: Tyler Smart Date: Sat, 13 Jun 2026 16:43:37 -0700 Subject: [PATCH 3/7] Remove unnecessary type helpers --- src/iterum/_iterum.py | 20 +++++--------------- src/iterum/_notset.py | 3 +-- src/iterum/_type_helpers.py | 28 ---------------------------- 3 files changed, 6 insertions(+), 45 deletions(-) delete mode 100644 src/iterum/_type_helpers.py diff --git a/src/iterum/_iterum.py b/src/iterum/_iterum.py index 424fdbc..c84b19c 100644 --- a/src/iterum/_iterum.py +++ b/src/iterum/_iterum.py @@ -11,13 +11,7 @@ from ._ordering import Ordering if TYPE_CHECKING: - from ._type_helpers import ( - SupportsMulT, - SupportsRichComparison, - SupportsRichComparisonT, - SupportsSumNoDefaultT, - ) - + from _typeshed import SupportsAdd, SupportsMul, SupportsRichComparison T_co = TypeVar("T_co", covariant=True) T = TypeVar("T") @@ -933,9 +927,7 @@ def map_while(self, predicate: Callable[[T_co], Option[U]], /) -> MapWhile[U]: """ return MapWhile(self, predicate) - def max( - self: Iterum[SupportsRichComparisonT], - ) -> Option[SupportsRichComparisonT]: + def max[C: SupportsRichComparison](self: Iterum[C]) -> Option[C]: """ Returns the maximum element of an iterum. @@ -1008,9 +1000,7 @@ def compare(x, y) -> Ordering: return self.max_by(compare) - def min( - self: Iterum[SupportsRichComparisonT], - ) -> Option[SupportsRichComparisonT]: + def min[C: SupportsRichComparison](self: Iterum[C]) -> Option[C]: """ Returns the minimum element of an iterum. @@ -1339,7 +1329,7 @@ def position(self, predicate: Callable[[T_co], object], /) -> Option[int]: return Some(i) return nil - def product(self: Iterum[SupportsMulT]) -> Option[SupportsMulT]: + def product[M: SupportsMul](self: Iterum[M]) -> Option[M]: """ Iterates over the entire iterum, multiplying all the elements @@ -1500,7 +1490,7 @@ def step_by(self, step: int, /) -> StepBy[T_co]: """ return StepBy(self, step) - def sum(self: Iterum[SupportsSumNoDefaultT]) -> Option[SupportsSumNoDefaultT]: + def sum[S: SupportsAdd](self: Iterum[S]) -> Option[S]: """ Sums the elements of an iterum. diff --git a/src/iterum/_notset.py b/src/iterum/_notset.py index ae62823..0e77853 100644 --- a/src/iterum/_notset.py +++ b/src/iterum/_notset.py @@ -1,7 +1,6 @@ from typing import TypeGuard from ._singleton import Singleton -from ._type_helpers import T class NotSetType(Singleton): @@ -12,7 +11,7 @@ def __instancecheck__(self, value): return value is self @classmethod - def is_set(cls, value: T | NotSetType) -> TypeGuard[T]: + def is_set[T](cls, value: T | NotSetType) -> TypeGuard[T]: return value is not cls() diff --git a/src/iterum/_type_helpers.py b/src/iterum/_type_helpers.py deleted file mode 100644 index 6e55cab..0000000 --- a/src/iterum/_type_helpers.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import TYPE_CHECKING, Any, Protocol, TypeVar - -if TYPE_CHECKING: - from _typeshed import SupportsAdd, SupportsRAdd, SupportsRichComparison - - -T = TypeVar("T") -T_co = TypeVar("T_co", covariant=True) -T_contra = TypeVar("T_contra", contravariant=True) - - -if TYPE_CHECKING: - SupportsRichComparisonT = TypeVar( - "SupportsRichComparisonT", bound=SupportsRichComparison - ) - - class SupportsMul(Protocol[T_contra, T_co]): - def __mul__(self, __x: T_contra) -> T_co: ... - - SupportsMulT = TypeVar("SupportsMulT", bound=SupportsMul) - - class SupportsSumWithNoDefaultGiven( - SupportsAdd[Any, Any], SupportsRAdd[int, Any], Protocol - ): ... - - SupportsSumNoDefaultT = TypeVar( - "SupportsSumNoDefaultT", bound=SupportsSumWithNoDefaultGiven - ) From 60e5b267ff6b43b5d74cfc91f4bc06b6c09b58bf Mon Sep 17 00:00:00 2001 From: Tyler Smart Date: Sat, 13 Jun 2026 16:48:57 -0700 Subject: [PATCH 4/7] Use new typevar syntax where possible --- src/iterum/_diterum.py | 6 ++-- src/iterum/_iterum.py | 63 +++++++++++++++++++++--------------------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/iterum/_diterum.py b/src/iterum/_diterum.py index 60b2173..5aef40c 100644 --- a/src/iterum/_diterum.py +++ b/src/iterum/_diterum.py @@ -1,7 +1,7 @@ from abc import abstractmethod from collections.abc import Callable, Sequence -from ._iterum import Iterum, T_co, U +from ._iterum import Iterum, T_co from ._option import Option, Some, nil @@ -200,7 +200,7 @@ def rfind(self, predicate: Callable[[T_co], object], /) -> Option[T_co]: """ return self.rev().find(predicate) - def rfold(self, init: U, f: Callable[[U, T_co], U], /) -> U: + def rfold[U](self, init: U, f: Callable[[U, T_co], U], /) -> U: """ A diterum method that reduces the diterum's elements to a single, final value, starting from the back. @@ -239,7 +239,7 @@ def rfold(self, init: U, f: Callable[[U, T_co], U], /) -> U: """ return self.rev().fold(init, f) - def try_rfold( + def try_rfold[U]( self, init: U, f: Callable[[U, T_co], U], diff --git a/src/iterum/_iterum.py b/src/iterum/_iterum.py index c84b19c..bbd94a5 100644 --- a/src/iterum/_iterum.py +++ b/src/iterum/_iterum.py @@ -14,9 +14,6 @@ from _typeshed import SupportsAdd, SupportsMul, SupportsRichComparison T_co = TypeVar("T_co", covariant=True) -T = TypeVar("T") -U = TypeVar("U") -V = TypeVar("V") class Iterum(Iterator[T_co]): @@ -249,14 +246,16 @@ def collect(self: Iterum[T_co], container: type[set], /) -> set[T_co]: ... def collect(self: Iterum[T_co], container: type[tuple], /) -> tuple[T_co, ...]: ... @overload - def collect(self: Iterum[tuple[U, V]], container: type[dict], /) -> dict[U, V]: ... + def collect[U, V]( + self: Iterum[tuple[U, V]], container: type[dict], / + ) -> dict[U, V]: ... @overload - def collect( + def collect[U]( self: Iterum[T_co], container: Callable[[Iterable[T_co]], U], / ) -> U: ... - def collect( # type: ignore + def collect[U]( # type: ignore self: Iterum[T_co], container: Callable[[Iterable[T_co]], U] = list, / ) -> U: """ @@ -405,7 +404,7 @@ def filter( """ return Filter(self, predicate) - def filter_map( + def filter_map[U]( self: Iterum[T_co], predicate: Callable[[T_co], Option[U]], / ) -> FilterMap[U]: """ @@ -478,7 +477,7 @@ def find(self, predicate: Callable[[T_co], object], /) -> Option[T_co]: return Some(x) return nil - def find_map(self, predicate: Callable[[T_co], Option[U]], /) -> Option[U]: + def find_map[U](self, predicate: Callable[[T_co], Option[U]], /) -> Option[U]: """ Applies function to the elements of iterum and returns the first non-nil result. @@ -504,7 +503,7 @@ def find_map(self, predicate: Callable[[T_co], Option[U]], /) -> Option[U]: """ return self.filter_map(predicate).next() - def flat_map(self, f: Callable[[T_co], Iterable[U]], /) -> FlatMap[U]: + def flat_map[U](self, f: Callable[[T_co], Iterable[U]], /) -> FlatMap[U]: """ Creates an iterum that works like map, but flattens nested structure. @@ -527,7 +526,7 @@ def flat_map(self, f: Callable[[T_co], Iterable[U]], /) -> FlatMap[U]: """ return FlatMap(self, f) - def flatten(self: Iterum[Iterable[U]]) -> Flatten[U]: + def flatten[U](self: Iterum[Iterable[U]]) -> Flatten[U]: """ Creates an iterum that flattens nested structure. @@ -554,7 +553,7 @@ def flatten(self: Iterum[Iterable[U]]) -> Flatten[U]: """ return Flatten(self) - def fold(self, init: U, f: Callable[[U, T_co], U], /) -> U: + def fold[U](self, init: U, f: Callable[[U, T_co], U], /) -> U: """ Folds every element into an accumulator by applying an operation, returning the final result. @@ -864,7 +863,7 @@ def lt( cmp = self.cmp(other) # type: ignore | reason: ask for forgiveness not permission return cmp == Ordering.Less - def map(self, f: Callable[[T_co], U], /) -> Map[U]: + def map[U](self, f: Callable[[T_co], U], /) -> Map[U]: """ Takes a closure and creates an iterum which calls that closure on each element. @@ -887,7 +886,7 @@ def map(self, f: Callable[[T_co], U], /) -> Map[U]: """ return Map(self, f) - def map_while(self, predicate: Callable[[T_co], Option[U]], /) -> MapWhile[U]: + def map_while[U](self, predicate: Callable[[T_co], Option[U]], /) -> MapWhile[U]: """ Creates an iterum that both yields elements based on a predicate and maps. @@ -1220,16 +1219,16 @@ def partition( ) -> tuple[tuple[T_co, ...], tuple[T_co, ...]]: ... @overload - def partition( + def partition[U, V]( self: Iterum[tuple[U, V]], f: Callable[[T_co], object], container: type[dict], / ) -> tuple[dict[U, V], dict[U, V]]: ... @overload - def partition( + def partition[U]( self, f: Callable[[T_co], object], container: Callable[[Iterable[T_co]], U], / ) -> tuple[U, U]: ... - def partition( # type: ignore + def partition[U]( # type: ignore self, f: Callable[[T_co], object], container: Callable[[Iterable[T_co]], U] = list, @@ -1377,7 +1376,9 @@ def reduce(self, f: Callable[[T_co, T_co], T_co], /) -> Option[T_co]: else: return Some(self.fold(first.unwrap(), f)) - def scan(self, init: U, f: Callable[[State[U], T_co], Option[V]], /) -> Scan[V]: + def scan[U, V]( + self, init: U, f: Callable[[State[U], T_co], Option[V]], / + ) -> Scan[V]: """ An iterum adapter which, like fold, holds internal state, but unlike fold, produces a new iterum. @@ -1608,7 +1609,7 @@ def take_while(self, predicate: Callable[[T_co], object], /) -> TakeWhile[T_co]: """ return TakeWhile(self, predicate) - def try_fold( + def try_fold[U]( self, init: U, f: Callable[[U, T_co], U], @@ -1670,31 +1671,31 @@ def try_fold( # return @overload - def unzip(self: Iterum[tuple[U, V]], /) -> tuple[list[U], list[V]]: ... + def unzip[U, V](self: Iterum[tuple[U, V]], /) -> tuple[list[U], list[V]]: ... @overload - def unzip( + def unzip[U, V]( self: Iterum[tuple[U, V]], container: type[list], / ) -> tuple[list[U], list[V]]: ... @overload - def unzip( + def unzip[U, V]( self: Iterum[tuple[U, V]], container: type[set], / ) -> tuple[set[U], set[V]]: ... @overload - def unzip( + def unzip[U, V]( self: Iterum[tuple[U, V]], container: type[tuple], / ) -> tuple[tuple[U, ...], tuple[V, ...]]: ... @overload - def unzip( + def unzip[U]( self: Iterum[tuple[object, object]], container: Callable[[Iterable[object]], U], /, ) -> tuple[U, U]: ... - def unzip( + def unzip[U]( self: Iterum[tuple[object, object]], container: Callable[[Iterable[object]], U] = list, /, @@ -1721,7 +1722,7 @@ def unzip( left, right = map(container, zip(*self, strict=False)) return left, right - def zip(self, other: Iterable[U], /) -> Zip[T_co, U]: + def zip[U](self, other: Iterable[U], /) -> Zip[T_co, U]: """ 'Zips up' two iterables into a single iterum of pairs. @@ -1834,7 +1835,7 @@ def __init__( class FlatMap(_IterumAdapter[T_co]): __slots__ = ("_iter",) - def __init__( + def __init__[U]( self, __iterable: Iterable[U], f: Callable[[U], Iterable[T_co]], / ) -> None: self._iter = iterum(__iterable).map(f).flatten() @@ -1843,7 +1844,7 @@ def __init__( class FilterMap(Iterum[T_co]): __slots__ = ("_iter", "_predicate") - def __init__( + def __init__[U]( self, __iterable: Iterable[U], predicate: Callable[[U], Option[T_co]], / ) -> None: self._iter = iterum(__iterable) @@ -1904,7 +1905,7 @@ def next(self) -> Option[T_co]: class Map(Iterum[T_co]): __slots__ = ("_f", "_iter") - def __init__(self, __iterable: Iterable[U], f: Callable[[U], T_co], /) -> None: + def __init__[U](self, __iterable: Iterable[U], f: Callable[[U], T_co], /) -> None: self._iter = iterum(__iterable) self._f = f @@ -1915,7 +1916,7 @@ def next(self) -> Option[T_co]: class MapWhile(Iterum[T_co]): __slots__ = ("_fuse", "_iter", "_predicate") - def __init__( + def __init__[U]( self, __iterable: Iterable[U], predicate: Callable[[U], Option[T_co]], / ) -> None: self._iter = iterum(__iterable) @@ -1979,7 +1980,7 @@ class State[T]: class Scan(Iterum[T_co]): __slots__ = ("_f", "_iter", "_state") - def __init__( + def __init__[U, V]( self, __iterable: Iterable[U], init: V, @@ -2092,7 +2093,7 @@ def next(self) -> Option[T_co]: return nil -class Zip(_IterumAdapter[tuple[U, V]]): +class Zip[U, V](_IterumAdapter[tuple[U, V]]): __slots__ = ("_iter",) def __init__(self, __iterable: Iterable[U], other: Iterable[V], /) -> None: From 5a100a331441d502561a2a5f4aae5100c6c9aff8 Mon Sep 17 00:00:00 2001 From: Tyler Smart Date: Sat, 13 Jun 2026 17:10:27 -0700 Subject: [PATCH 5/7] Simplify notset and add note about future sentinel replacement --- src/iterum/_notset.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/iterum/_notset.py b/src/iterum/_notset.py index 0e77853..bfb747a 100644 --- a/src/iterum/_notset.py +++ b/src/iterum/_notset.py @@ -1,18 +1,10 @@ -from typing import TypeGuard - from ._singleton import Singleton +# TODO: Replace with typing.sentinel once available +# and better supported. class NotSetType(Singleton): __slots__ = () - # def __instancecheck__(self, value: object | NotSetType) -> TypeGuard[Self]: - def __instancecheck__(self, value): - return value is self - - @classmethod - def is_set[T](cls, value: T | NotSetType) -> TypeGuard[T]: - return value is not cls() - NotSet = NotSetType() From 7705af9cf046404f9e9eaebafe11e6fa65083cd8 Mon Sep 17 00:00:00 2001 From: Tyler Smart Date: Sat, 13 Jun 2026 17:15:29 -0700 Subject: [PATCH 6/7] Explicit override --- src/iterum/_diterum.py | 7 +++++++ src/iterum/_iterum.py | 17 ++++++++++++++++- src/iterum/_seq.py | 6 +++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/iterum/_diterum.py b/src/iterum/_diterum.py index 5aef40c..1cdb959 100644 --- a/src/iterum/_diterum.py +++ b/src/iterum/_diterum.py @@ -1,5 +1,6 @@ from abc import abstractmethod from collections.abc import Callable, Sequence +from typing import override from ._iterum import Iterum, T_co from ._option import Option, Some, nil @@ -276,12 +277,15 @@ class Rev(Diterum[T_co]): def __init__(self, __x: Diterum[T_co] | Sequence[T_co]) -> None: self._x = __x if isinstance(__x, Diterum) else diterum(__x) + @override def next(self) -> Option[T_co]: return self._x.next_back() + @override def next_back(self) -> Option[T_co]: return self._x.next() + @override def len(self) -> int: return self._x.len() @@ -329,6 +333,7 @@ def __init__(self, __seq: Sequence[T_co], /) -> None: self._front = 0 self._back = len(__seq) - 1 + @override def next(self) -> Option[T_co]: """ Returns the next value in the sequence from the front if present, @@ -352,6 +357,7 @@ def next(self) -> Option[T_co]: self._front += 1 return Some(nxt) + @override def next_back(self) -> Option[T_co]: """ Returns the next value in the sequence from the back if present, @@ -375,6 +381,7 @@ def next_back(self) -> Option[T_co]: self._back -= 1 return Some(nxt) + @override def len(self) -> int: """ Returns the remaining length of the sequence. diff --git a/src/iterum/_iterum.py b/src/iterum/_iterum.py index bbd94a5..8d754f0 100644 --- a/src/iterum/_iterum.py +++ b/src/iterum/_iterum.py @@ -3,7 +3,7 @@ from abc import abstractmethod from collections.abc import Callable, Iterable, Iterator from dataclasses import dataclass -from typing import TYPE_CHECKING, TypeVar, overload +from typing import TYPE_CHECKING, TypeVar, overload, override from ._helpers import check_methods from ._notset import NotSet, NotSetType @@ -77,6 +77,7 @@ def __subclasshook__(cls, C): return check_methods(C, "next") return NotImplemented + @override def __next__(self) -> T_co: return self.next().ok_or_else(StopIteration) @@ -1798,6 +1799,7 @@ class _IterumAdapter(Iterum[T_co]): __slots__ = () _iter: Iterator[T_co] + @override def next(self) -> Option[T_co]: return _try_next(self._iter) @@ -1850,6 +1852,7 @@ def __init__[U]( self._iter = iterum(__iterable) self._predicate = predicate + @override def next(self) -> Option[T_co]: while True: x = self._iter.next() @@ -1875,6 +1878,7 @@ def __init__(self, __iterable: Iterable[T_co]) -> None: self._iter = iterum(__iterable) self._fuse = True + @override def next(self) -> Option[T_co]: if not self._fuse: return nil @@ -1896,6 +1900,7 @@ def __init__( self._iter = iterum(__iterable) self._f = f + @override def next(self) -> Option[T_co]: nxt = self._iter.next() nxt.map(self._f) @@ -1909,6 +1914,7 @@ def __init__[U](self, __iterable: Iterable[U], f: Callable[[U], T_co], /) -> Non self._iter = iterum(__iterable) self._f = f + @override def next(self) -> Option[T_co]: return self._iter.next().map(self._f) @@ -1923,6 +1929,7 @@ def __init__[U]( self._predicate = predicate self._fuse = True + @override def next(self) -> Option[T_co]: if not self._fuse: return nil @@ -1941,6 +1948,7 @@ def __init__(self, __iterable: Iterable[T_co], /) -> None: self._iter = iterum(__iterable) self._peek: Option[T_co] | NotSetType = NotSet + @override def next(self) -> Option[T_co]: if isinstance(self._peek, NotSetType): return self._iter.next() @@ -1991,6 +1999,7 @@ def __init__[U, V]( self._state = State(init) self._f = f + @override def next(self) -> Option[T_co]: def scan(val) -> Option[T_co]: return self._f(self._state, val) @@ -2010,6 +2019,7 @@ def __init__( self._iter = iterum(__iterable) self._n = n + @override def next(self) -> Option[T_co]: if self._n: self._iter.nth(self._n - 1) @@ -2031,6 +2041,7 @@ def __init__( self._predicate = predicate self._fuse = True + @override def next(self) -> Option[T_co]: if not self._fuse: return self._iter.next() @@ -2053,6 +2064,7 @@ def __init__(self, __iterable: Iterable[T_co], step: int, /) -> None: self._iter = iterum(__iterable).enumerate() self._step = step + @override def next(self) -> Option[T_co]: idx, nxt = self._iter.next().unzip() while nxt.is_some() and idx.is_some_and(lambda idx: idx % self._step): @@ -2069,6 +2081,7 @@ def __init__(self, __iterable: Iterable[T_co], n: int, /) -> None: self._max = n self._idx = 0 + @override def next(self) -> Option[T_co]: if self._idx >= self._max: return nil @@ -2086,6 +2099,7 @@ def __init__( self._iter = iterum(__iterable) self._predicate = predicate + @override def next(self) -> Option[T_co]: nxt = self._iter.next() if nxt.is_some_and(self._predicate): @@ -2138,6 +2152,7 @@ class iterum(Iterum[T_co]): def __init__(self, __iterable: Iterable[T_co], /) -> None: self._iter = iter(__iterable) + @override def next(self) -> Option[T_co]: """ Returns the next value in the iterable if present, otherwise [nil][iterum.nil]. diff --git a/src/iterum/_seq.py b/src/iterum/_seq.py index eaa5e34..59df818 100644 --- a/src/iterum/_seq.py +++ b/src/iterum/_seq.py @@ -1,6 +1,6 @@ import operator from types import EllipsisType -from typing import Literal, SupportsIndex, overload +from typing import Literal, SupportsIndex, overload, override from ._diterum import Diterum from ._iterum import Iterum @@ -150,6 +150,7 @@ def __init__(self, *, start: int, end: int, step: int) -> None: self._step = step self._dir = _sign(step) + @override def next(self) -> Option[int]: if self._dir * (self._back - self._front) < 0: return nil @@ -158,6 +159,7 @@ def next(self) -> Option[int]: self._front += self._step return nxt + @override def next_back(self) -> Option[int]: if self._dir * (self._back - self._front) < 0: return nil @@ -166,6 +168,7 @@ def next_back(self) -> Option[int]: self._back -= self._step return nxt_bk + @override def len(self) -> int: if self._dir * (self._back - self._front) < 0: return 0 @@ -201,6 +204,7 @@ def __init__(self, *, start: int, step: int) -> None: self._front = start self._step = step + @override def next(self) -> Option[int]: nxt = Some(self._front) self._front += self._step From d941d6626003e17ac494b774a22f889f6fa67a2f Mon Sep 17 00:00:00 2001 From: Tyler Smart Date: Sat, 13 Jun 2026 17:40:40 -0700 Subject: [PATCH 7/7] Add is_nil and is_some for better type support --- docs/api.md | 3 +++ src/iterum/__init__.py | 14 +++++++++++++- src/iterum/_option.py | 35 +++++++++++++++++++++++++++++++++++ src/iterum/_seq.py | 2 +- type_tests/type_nil.py | 11 +++++++++-- type_tests/type_option.py | 16 +++++++++++++++- type_tests/type_some.py | 11 +++++++++-- 7 files changed, 85 insertions(+), 7 deletions(-) diff --git a/docs/api.md b/docs/api.md index f516198..581d69b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -31,6 +31,9 @@ ::: iterum.Nil ::: iterum.nil +::: iterum.is_some +::: iterum.is_nil + ::: iterum.ExpectNilError ::: iterum.UnwrapNilError diff --git a/src/iterum/__init__.py b/src/iterum/__init__.py index 345ba0c..276e57d 100644 --- a/src/iterum/__init__.py +++ b/src/iterum/__init__.py @@ -23,7 +23,17 @@ Zip, iterum, ) -from ._option import ExpectNilError, Nil, Option, Some, Swap, UnwrapNilError, nil +from ._option import ( + ExpectNilError, + Nil, + Option, + Some, + Swap, + UnwrapNilError, + is_nil, + is_some, + nil, +) from ._ordering import Ordering from ._seq import InfSeq, Seq, seq @@ -42,6 +52,8 @@ "nil", "ExpectNilError", "UnwrapNilError", + "is_nil", + "is_some", # Ordering "Ordering", # special Iterum implementations diff --git a/src/iterum/_option.py b/src/iterum/_option.py index 494f5b6..b186043 100644 --- a/src/iterum/_option.py +++ b/src/iterum/_option.py @@ -5,6 +5,7 @@ Literal, NamedTuple, NoReturn, + TypeIs, overload, ) @@ -1484,3 +1485,37 @@ def zip[U](self, other: Option[U], /) -> Option[tuple[T, U]]: ``` """ + + +def is_some[T](o: Option[T], /) -> TypeIs[Some[T]]: + """Returns `True` if the option is a Some value. + + **Examples:** + + ```python + >>> assert is_some(Some(2)) is True + >>> assert is_some(nil) is False + + ``` + + Note: This method is provided as a standalone function in order + to leverage `TypeIs`, which provides better typing. + """ + return o is not nil + + +def is_nil[T](o: Option[T], /) -> TypeIs[Nil]: + """Returns `True` if the option is a [nil][iterum.nil] value. + + **Examples:** + + ```python + >>> assert is_nil(Some(2)) is False + >>> assert is_nil(nil) is True + + ``` + + Note: This method is provided as a standalone function in order + to leverage `TypeIs`, which provides better typing. + """ + return o is nil diff --git a/src/iterum/_seq.py b/src/iterum/_seq.py index 59df818..a24fecd 100644 --- a/src/iterum/_seq.py +++ b/src/iterum/_seq.py @@ -213,7 +213,7 @@ def next(self) -> Option[int]: def __repr__(self) -> str: return f"{type(self).__name__}(start={self._front}, step={self._step})" - def __bool__(self) -> bool: + def __bool__(self) -> Literal[True]: return True def __eq__(self, other: object) -> bool: diff --git a/type_tests/type_nil.py b/type_tests/type_nil.py index c8b60f3..751724f 100644 --- a/type_tests/type_nil.py +++ b/type_tests/type_nil.py @@ -1,6 +1,6 @@ -from typing import Literal, NoReturn, TypeVar, assert_type +from typing import Literal, Never, NoReturn, TypeVar, assert_type -from iterum import Nil, Option, Some, Swap, iterum, nil +from iterum import Nil, Option, Some, Swap, is_nil, iterum, nil from .option_helpers import ( create_nil, @@ -123,3 +123,10 @@ def nil_xor(): def nil_zip(): assert_type(nil.zip(Some(1)), Nil) + + +def nil_is_nil_standalone(): + if is_nil(nil): + assert_type(nil, Nil) + else: + assert_type(nil, Never) diff --git a/type_tests/type_option.py b/type_tests/type_option.py index 09b9a27..ff50ed8 100644 --- a/type_tests/type_option.py +++ b/type_tests/type_option.py @@ -1,6 +1,6 @@ from typing import Any, TypeVar, assert_type -from iterum import Nil, Option, Some, Swap, iterum, nil +from iterum import Nil, Option, Some, Swap, is_nil, is_some, iterum, nil from .option_helpers import ( create_nil, @@ -157,3 +157,17 @@ def option_xor(): def option_zip(): assert_type(option.zip(Some("test")), Option[tuple[int, str]]) assert_type(option.zip(nil), Nil) + + +def option_is_some_standalone(): + if is_some(option): + assert_type(option, Some[int]) + else: + assert_type(option, Nil) + + +def option_is_nil_standalone(): + if is_nil(option): + assert_type(option, Nil) + else: + assert_type(option, Some[int]) diff --git a/type_tests/type_some.py b/type_tests/type_some.py index f141a5d..353bc41 100644 --- a/type_tests/type_some.py +++ b/type_tests/type_some.py @@ -1,6 +1,6 @@ -from typing import Literal, TypeVar, assert_type +from typing import Literal, Never, TypeVar, assert_type -from iterum import Nil, Option, Some, Swap, iterum, nil +from iterum import Nil, Option, Some, Swap, is_some, iterum, nil from .option_helpers import ( create_nil, @@ -138,3 +138,10 @@ def some_xor(): def some_zip(): assert_type(some.zip(Some("test")), Some[tuple[int, str]]) assert_type(some.zip(nil), Nil) + + +def some_is_some_standalone(): + if is_some(some): + assert_type(some, Some[int]) + else: + assert_type(some, Never)