Repository: MaineKuehn/usim

Issue:
Rename/simplify usim.each
The ``usim.each`` has now repeatedly led to confusion. It does not integrate well with ``async for`` wording (``for each`` also means different things in e.g. C++) and having to use keywords makes the ``each`` word redundant. We could split ``each`` into its two use cases and name them directly:

```
# old: specify interval *or* delay
async for delta in each(interval=10):
    ...

# new
async for delta in interval(10):
    ...
```

Discussion:

Patch:
diff --git a/README.rst b/README.rst
index 136bffb..325a402 100644
--- a/README.rst
+++ b/README.rst
@@ -9,10 +9,10 @@ Using the ``async``/``await`` capabilities of Python3,
 
 .. code:: python3
 
-   >>> from usim import each, run
+   >>> from usim import delay, run
    >>>
    >>> async def metronome(period: float, sound: str):
-   ...     async for now in each(delay=period):
+   ...     async for now in delay(period):
    ...         print(sound, '@', now)
    ...
    >>> run(metronome(period=1, sound='tick'), metronome(period=2, sound='TOCK'), till=5)
diff --git a/docs/index.rst b/docs/index.rst
index deb7381..09225cc 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -27,10 +27,10 @@ no matter if they are small and simple, or large and complex.
 
 .. code:: python3
 
-   >>> from usim import each, run
+   >>> from usim import delay, run
    >>>
    >>> async def metronome(period: float, sound: str):
-   ...     async for now in each(delay=period):
+   ...     async for now in delay(period):
    ...         print(sound, '@', now)
    ...
    >>> run(metronome(period=1, sound='tick'), metronome(period=2, sound='TOCK'), till=5)
diff --git a/docs/source/api/overview.rst b/docs/source/api/overview.rst
index 082cdb3..0ac8c1c 100644
--- a/docs/source/api/overview.rst
+++ b/docs/source/api/overview.rst
@@ -21,7 +21,8 @@ Simulating Time
 :py:data:`usim.time`     used to define or delay until arbitrary points in time
 :py:data:`usim.eternity` a delay time indefinitely in the future
 :py:data:`usim.instant`  a delay time indistinguishable from the current time
-:py:data:`usim.each`     a repeated delay by a fixed interval
+:py:data:`usim.interval` a repeated delay by a fixed interval
+:py:data:`usim.delay`    a repeated delay
 ======================== ======================================================
 
 Detailed Topics
diff --git a/docs/source/api/timing.rst b/docs/source/api/timing.rst
index bba04f8..2daeff7 100644
--- a/docs/source/api/timing.rst
+++ b/docs/source/api/timing.rst
@@ -10,7 +10,8 @@ It gives access to the current time, and using it in expressions provides
 For convenience and readability, several helpers are provided:
 :py:data:`usim.eternity` and :py:data:`usim.instant` are the longest and shortest
 delay possible, respectively.
-:py:func:`usim.each` allows to repeatedly delay in order to act at fixed intervals.
+:py:func:`usim.delay` and :py:func:`usim.interval` allow to repeatedly delay
+in order to act at fixed intervals.
 
 Direct Interaction with Time
 ----------------------------
@@ -30,5 +31,8 @@ Actively Postpone and Suspend
 Controlled Repetitions
 ----------------------
 
-.. autofunction:: usim.each
+.. autofunction:: usim.interval
+    :async-for:
+
+.. autofunction:: usim.delay
     :async-for:
diff --git a/usim/__about__.py b/usim/__about__.py
index a3f28e1..8922ca6 100644
--- a/usim/__about__.py
+++ b/usim/__about__.py
@@ -10,10 +10,10 @@ Using the ``async``/``await`` capabilities of Python3,
 
 .. code:: python3
 
-   >>> from usim import each, run
+   >>> from usim import delay, run
    >>>
    >>> async def metronome(period: float, sound: str):
-   ...     async for now in each(delay=period):
+   ...     async for now in delay(period):
    ...         print(sound, '@', now)
    ...
    >>> run(metronome(period=1, sound='tick'), metronome(period=2, sound='TOCK'), till=5)
diff --git a/usim/__init__.py b/usim/__init__.py
index 7694240..61f1cf6 100644
--- a/usim/__init__.py
+++ b/usim/__init__.py
@@ -2,7 +2,7 @@ from typing import Coroutine
 
 from .__about__ import __version__  # noqa: F401
 from ._core.loop import Loop as _Loop
-from ._primitives.timing import Time, Eternity, Instant, each
+from ._primitives.timing import Time, Eternity, Instant, interval, delay
 from ._primitives.flag import Flag
 from ._primitives.locks import Lock
 from ._primitives.context import until, Scope, VolatileTaskClosed
@@ -12,7 +12,7 @@ from ._primitives.concurrent_exception import Concurrent
 
 __all__ = [
     'run',
-    'time', 'eternity', 'instant', 'each',
+    'time', 'eternity', 'instant', 'interval', 'delay',
     'until', 'Scope', 'TaskCancelled', 'VolatileTaskClosed', 'TaskClosed', 'TaskState',
     'Flag', 'Lock',
     'Concurrent',
diff --git a/usim/_primitives/timing.py b/usim/_primitives/timing.py
index ea750a2..1398e79 100644
--- a/usim/_primitives/timing.py
+++ b/usim/_primitives/timing.py
@@ -403,63 +403,62 @@ class Time:
 time = Time()
 
 
-async def each_interval(interval: float):
+async def interval(period) -> AsyncIterable[float]:
+    """
+    Iterate through time by intervals of ``period``
+
+    :param period: on each step, pause with a ``period`` since the last step
+
+    Asynchronous iteration pauses and provides the current time at each step.
+
+    .. code:: python3
+
+        print('It was', time.now)  # 0
+        async for now in interval(10):
+            await (time + 1)
+            print(now, time.now)  # (10, 11), (20, 21), (30, 31), ...
+
+    The first pause occurs *before* entering the loop body.
+
+    Using interval causes iteration to *resume at* regular times,
+    even if the current activity is :term:`suspended <Suspension>`
+    in the loop body - the pause is shortened if necessary.
+
+    .. seealso:: :py:func:`~.delay` if you want to always *pause for* the same time
+    """
     loop = __LOOP_STATE__.LOOP
     last_time = loop.time
     while True:
-        await (time == last_time + interval)
+        await (time == last_time + period)
         last_time = loop.time
         yield last_time
 
 
-async def each_delay(delay: float):
-    loop = __LOOP_STATE__.LOOP
-    waiter = time + delay
-    while True:
-        await waiter
-        yield loop.time
-
-
-def each(
-        *, delay: float = None, interval: float = None
-) -> AsyncIterable[float]:
+async def delay(period) -> AsyncIterable[float]:
     """
-    Iterate through time by either ``delay`` or ``interval``
+    Iterate through time by delays of ``period``
 
-    :param delay: on each step, pause for ``delay``
-    :param interval: on each step, pause until ``interval`` since the last step
-    :raises TypeError: if both ``delay`` and ``interval`` are provided
+    :param period: on each step, pause for a ``period``
 
     Asynchronous iteration pauses and provides the current time at each step.
 
     .. code:: python3
 
         print('It was', time.now)  # 0
-        async for now in each(delay=10):
-            print('It is', now)  # 10, 20, 30, ...
+        async for now in delay(10):
+            await (time + 1)
+            print(now, time.now)  # (10, 11), (21, 22), (32, 33), ...
 
     The first pause occurs *before* entering the loop body.
 
-    Setting `delay`` causes iteration to always *pause for* the same time,
+    Delaying causes iteration to always *pause for* the same time,
     even if the current activity is :term:`suspended <Suspension>`
     in the loop body.
-    Setting ``interval`` causes iteration to *resume at* regular times,
-    even if the current activity is :term:`suspended <Suspension>`
-    in the loop body - the pause is shortened if necessary.
-
-    .. code:: python3
 
-        async for now in each(interval=10):
-            await (time + 1)
-            print(now, time.now)  # (10, 11), (20, 21), (30, 31), ...
-
-        async for now in each(delay=10):
-            await (time + 1)
-            print(now, time.now)  # (10, 11), (21, 22), (32, 33), ...
+    .. seealso:: :py:func:`~.interval` if you want to *resume at* regular times
     """
-    if delay is not None and interval is None:
-        return each_delay(delay)
-    elif interval is not None and delay is None:
-        return each_interval(interval)
-    else:
-        raise TypeError("each() got conflicting arguments 'delay' and 'interval'")
+    loop = __LOOP_STATE__.LOOP
+    waiter = time + period
+    while True:
+        await waiter
+        yield loop.time


Test patch:
diff --git a/usim_pytest/test_scopes.py b/usim_pytest/test_scopes.py
index d8df20e..5dbc96f 100644
--- a/usim_pytest/test_scopes.py
+++ b/usim_pytest/test_scopes.py
@@ -1,7 +1,7 @@
 import pytest
 
 from usim import Scope, time, eternity, VolatileTaskClosed, TaskState,\
-    TaskCancelled, TaskClosed, until, each
+    TaskCancelled, TaskClosed, until, interval
 
 from .utility import via_usim, assertion_mode
 
@@ -230,7 +230,7 @@ async def test_order_with_cancel():
 async def test_for_interval():
     expected_time = 5
     async with until(time == 60):
-        async for _ in each(interval=5):
+        async for _ in interval(5):
             assert time.now == expected_time
             expected_time += 5
     assert time.now == 60
diff --git a/usim_pytest/test_types/test_time.py b/usim_pytest/test_types/test_time.py
index 009d84c..a9bcfc9 100644
--- a/usim_pytest/test_types/test_time.py
+++ b/usim_pytest/test_types/test_time.py
@@ -2,7 +2,7 @@ import math
 
 import pytest
 
-from usim import time, until, eternity, instant, each
+from usim import time, until, eternity, instant, delay, interval
 
 from ..utility import via_usim
 
@@ -140,7 +140,7 @@ class TestTimeIteration:
     @via_usim
     async def test_delay(self):
         start, iteration = time.now, 0
-        async for now in each(delay=20):
+        async for now in delay(20):
             iteration += 1
             await (time + 5)
             assert time.now - now == 5
@@ -151,16 +151,10 @@ class TestTimeIteration:
     @via_usim
     async def test_interval(self):
         start, iteration = time.now, 1
-        async for now in each(interval=20):
+        async for now in interval(20):
             await (time + 5)
             assert time.now - now == 5
             assert time.now == start + iteration * 20 + 5
             if iteration == 5:
                 break
             iteration += 1
-
-    @via_usim
-    async def test_misue(self):
-        with pytest.raises(TypeError):
-            async for _ in each(interval=20, delay=20):
-                assert False