Skip to content

Instantly share code, notes, and snippets.

@BeautyyuYanli
Last active December 1, 2024 00:10
Show Gist options
  • Select an option

  • Save BeautyyuYanli/0a6891d8959bcf06b5cd2e5bd30ef3d1 to your computer and use it in GitHub Desktop.

Select an option

Save BeautyyuYanli/0a6891d8959bcf06b5cd2e5bd30ef3d1 to your computer and use it in GitHub Desktop.

Revisions

  1. BeautyyuYanli revised this gist Nov 30, 2024. 1 changed file with 86 additions and 48 deletions.
    134 changes: 86 additions & 48 deletions cwaitable.py
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,13 @@
    import asyncio
    from concurrent.futures import ThreadPoolExecutor
    from typing import Optional, Generic, TypeVar, Awaitable, Generator, Any, Union
    from queue import Queue

    T = TypeVar("T")
    from typing import (
    Any,
    Awaitable,
    Generator,
    Generic,
    Tuple,
    TypeVar,
    Union,
    )

    """
    We have invented a new type of function, called `CWaitable` function,
    @@ -15,18 +19,29 @@
    The yellow function can call all colors of functions.
    To call a red (async) function, it will yield the Awaitable[T]
    until the top-level event loop, and get the result T to continue.
    to the top-level event loop, and get the result T to continue.
    To call a blue (sync) function, it will yield the result T directly.
    To call a blue (sync) function, it will simply call and return,
    with a Generator wrapper.
    To call a yellow function, it choose to yield or not, by the return type.
    To call a yellow function, it will simply call and, pass the yield
    to the top-level event loop, then/or get the result T.
    The difference between yellow and red/async functions, is that
    red/async functions always be submitted to the event loop.
    But yellow functions will only submit when encountering `yield Awaitable`.
    red/async functions are always submitted to the event loop.
    But yellow functions will only be submitted when encountering
    `yield Awaitable`.
    Otherwise, it simply call yellow functions in the stack.
    To convert a blue/sync function to a yellow function, we need to
    wrap the return value with `CWaitValue`, and find the caller chain to
    use `yield from` to call the yellow function. This still introduces
    the problem of coloring: just use yellow instead of red.
    """

    T = TypeVar("T")
    CWaitable = Generator[Awaitable[Any], Any, T]


    class CWaitValue(Generator[Any, Any, T], Generic[T]):

    @@ -40,64 +55,87 @@ def send(self, value: Any) -> T:
    raise StopIteration(self.value)

    def throw(self, typ: Any, val: Any = None, tb: Any = None):
    raise StopIteration(self.value)
    raise RuntimeError("CWaitValue should not be thrown")


    CWaitable = Generator[Awaitable[Any], Any, T]
    async def await_c(cwaitable: Union[CWaitable[T], CWaitValue[T]]) -> T:
    print("await_c start")

    try:
    x = next(cwaitable)
    except StopIteration as e:
    return e.value

    cnt = 1
    while True:
    try:
    print(f"await_c submit async func {cnt} times")
    result = await x # type: ignore
    x = cwaitable.send(result)
    cnt += 1
    except StopIteration as e:
    print("await_c ends")
    return e.value


    async def red_task(x: int):
    print("red_task start")
    await asyncio.sleep(x)
    print("red_task ends")
    return x


    def blue_task(x: int):
    return x


    # Yellow function to call red function
    # The keyword is `yield`
    def yellow_call_red(x: int, y: int) -> CWaitable[int]:
    async def red_task(x: int):
    print("red_task start")
    await asyncio.sleep(x)
    print("red_task ends")
    return x

    def yellow_call_red(x: int) -> CWaitable[int]:
    print("yellow_call_red start")

    res1 = yield red_task(x)
    res2 = yield red_task(y)

    print("yellow_call_red ends")
    return res1 + res2
    return res1


    # Yellow function to call blue function. Using CWaitValue to wrap the result.
    def yellow_call_blue(x: int, y: int):
    def blue_task(x: int, y: int):
    return x + y

    ans = blue_task(x, y)
    def yellow_call_blue(x: int):
    print("yellow_call_blue")
    ans = blue_task(x)
    return CWaitValue(ans)


    # Yellow function to call yellow function
    # Yellow function to call yellow function in sync manner
    # The keyword is `yield from`
    def yellow_call_yellow(x: int, y: int) -> CWaitable[int]:
    def yellow_call_yellow(x: int, y: int, z: int) -> CWaitable[int]:
    print("yellow_call_yellow start")
    res1 = yield from yellow_call_red(x, y)
    res2 = yield from yellow_call_blue(x, y)
    res1 = yield from yellow_call_red(x)
    res2 = yield from yellow_call_red(y)
    res3 = yield from yellow_call_blue(z)
    print("yellow_call_yellow ends")
    return res1 + res2
    return res1 + res2 + res3


    async def a_wait_c(cwaitable: CWaitable[T]) -> T:
    print("a_wait_c start")

    x = next(cwaitable)
    cnt = 1
    while True:
    try:
    print(f"a_wait_c submit async func {cnt} times")
    result = await x
    x = cwaitable.send(result)
    cnt += 1
    except StopIteration as e:
    print("a_wait_c ends")
    return e.value
    # Yellow function to call yellow function in async manner
    # The keyword is `yield await_c(...)`
    def yellow_call_yellow_async(x: int, y: int, z: int) -> CWaitable[int]:
    print("yellow_call_yellow_async start")
    res: Tuple[int, int, int] = yield asyncio.gather(
    await_c(yellow_call_red(x)),
    await_c(yellow_call_red(y)),
    await_c(yellow_call_blue(z)),
    )
    print("yellow_call_yellow_async ends")
    return res[0] + res[1] + res[2]


    if __name__ == "__main__":
    print(asyncio.run(a_wait_c(yellow_call_yellow(1, 2))))
    from time import time

    t0 = time()
    print(asyncio.run(await_c(yellow_call_yellow(1, 2, 3))))
    t1 = time()
    print(f"====== yellow_call_yellow cost {t1 - t0} seconds ======")

    print(asyncio.run(await_c(yellow_call_yellow_async(1, 2, 3))))
    print(f"====== yellow_call_yellow_async cost {time() - t1} seconds ======")
  2. BeautyyuYanli revised this gist Nov 30, 2024. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions cwaitable.py
    Original file line number Diff line number Diff line change
    @@ -49,14 +49,14 @@ def throw(self, typ: Any, val: Any = None, tb: Any = None):
    # Yellow function to call red function
    # The keyword is `yield`
    def yellow_call_red(x: int, y: int) -> CWaitable[int]:
    print("yellow_call_red start")

    async def red_task(x: int):
    print("red_task start")
    await asyncio.sleep(x)
    print("red_task ends")
    return x

    print("yellow_call_red start")

    res1 = yield red_task(x)
    res2 = yield red_task(y)

  3. BeautyyuYanli revised this gist Nov 30, 2024. 1 changed file with 22 additions and 24 deletions.
    46 changes: 22 additions & 24 deletions cwaitable.py
    Original file line number Diff line number Diff line change
    @@ -4,8 +4,6 @@
    from queue import Queue

    T = TypeVar("T")
    T1 = TypeVar("T1")
    T2 = TypeVar("T2")

    """
    We have invented a new type of function, called `CWaitable` function,
    @@ -29,7 +27,23 @@
    Otherwise, it simply call yellow functions in the stack.
    """

    CWaitable = Union[Generator[Awaitable[Any], Any, T], T]

    class CWaitValue(Generator[Any, Any, T], Generic[T]):

    def __init__(self, value: T):
    self.value = value

    def __next__(self) -> T:
    raise StopIteration(self.value)

    def send(self, value: Any) -> T:
    raise StopIteration(self.value)

    def throw(self, typ: Any, val: Any = None, tb: Any = None):
    raise StopIteration(self.value)


    CWaitable = Generator[Awaitable[Any], Any, T]


    # Yellow function to call red function
    @@ -50,43 +64,27 @@ async def red_task(x: int):
    return res1 + res2


    # Yellow function to call blue function
    def yellow_call_blue(x: int, y: int) -> CWaitable[int]:
    print("yellow_call_blue start")

    # Yellow function to call blue function. Using CWaitValue to wrap the result.
    def yellow_call_blue(x: int, y: int):
    def blue_task(x: int, y: int):
    return x + y

    ans = blue_task(x, y)
    print("yellow_call_blue ends")
    return ans
    return CWaitValue(ans)


    # Yellow function to call yellow function
    # The keyword is `yield from`
    def yellow_call_yellow(x: int, y: int) -> CWaitable[int]:
    print("yellow_call_yellow start")
    tmp1 = yellow_call_red(x, y)
    if isinstance(tmp1, Generator):
    res1 = yield from tmp1
    else:
    res1 = tmp1

    tmp2 = yellow_call_blue(x, y)
    if isinstance(tmp2, Generator):
    res2 = yield from tmp2
    else:
    res2 = tmp2

    res1 = yield from yellow_call_red(x, y)
    res2 = yield from yellow_call_blue(x, y)
    print("yellow_call_yellow ends")
    return res1 + res2


    async def a_wait_c(cwaitable: CWaitable[T]) -> T:
    print("a_wait_c start")
    if not isinstance(cwaitable, Generator):
    print("a_wait_c ends")
    return cwaitable

    x = next(cwaitable)
    cnt = 1
  4. BeautyyuYanli created this gist Nov 30, 2024.
    105 changes: 105 additions & 0 deletions cwaitable.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,105 @@
    import asyncio
    from concurrent.futures import ThreadPoolExecutor
    from typing import Optional, Generic, TypeVar, Awaitable, Generator, Any, Union
    from queue import Queue

    T = TypeVar("T")
    T1 = TypeVar("T1")
    T2 = TypeVar("T2")

    """
    We have invented a new type of function, called `CWaitable` function,
    or "yellow function".
    Yellow functions must be runned in an async event loop environment.
    Or we say, the async event loop is the runtime of yellow functions.
    The yellow function can call all colors of functions.
    To call a red (async) function, it will yield the Awaitable[T]
    until the top-level event loop, and get the result T to continue.
    To call a blue (sync) function, it will yield the result T directly.
    To call a yellow function, it choose to yield or not, by the return type.
    The difference between yellow and red/async functions, is that
    red/async functions always be submitted to the event loop.
    But yellow functions will only submit when encountering `yield Awaitable`.
    Otherwise, it simply call yellow functions in the stack.
    """

    CWaitable = Union[Generator[Awaitable[Any], Any, T], T]


    # Yellow function to call red function
    # The keyword is `yield`
    def yellow_call_red(x: int, y: int) -> CWaitable[int]:
    print("yellow_call_red start")

    async def red_task(x: int):
    print("red_task start")
    await asyncio.sleep(x)
    print("red_task ends")
    return x

    res1 = yield red_task(x)
    res2 = yield red_task(y)

    print("yellow_call_red ends")
    return res1 + res2


    # Yellow function to call blue function
    def yellow_call_blue(x: int, y: int) -> CWaitable[int]:
    print("yellow_call_blue start")

    def blue_task(x: int, y: int):
    return x + y

    ans = blue_task(x, y)
    print("yellow_call_blue ends")
    return ans


    # Yellow function to call yellow function
    # The keyword is `yield from`
    def yellow_call_yellow(x: int, y: int) -> CWaitable[int]:
    print("yellow_call_yellow start")
    tmp1 = yellow_call_red(x, y)
    if isinstance(tmp1, Generator):
    res1 = yield from tmp1
    else:
    res1 = tmp1

    tmp2 = yellow_call_blue(x, y)
    if isinstance(tmp2, Generator):
    res2 = yield from tmp2
    else:
    res2 = tmp2

    print("yellow_call_yellow ends")
    return res1 + res2


    async def a_wait_c(cwaitable: CWaitable[T]) -> T:
    print("a_wait_c start")
    if not isinstance(cwaitable, Generator):
    print("a_wait_c ends")
    return cwaitable

    x = next(cwaitable)
    cnt = 1
    while True:
    try:
    print(f"a_wait_c submit async func {cnt} times")
    result = await x
    x = cwaitable.send(result)
    cnt += 1
    except StopIteration as e:
    print("a_wait_c ends")
    return e.value


    if __name__ == "__main__":
    print(asyncio.run(a_wait_c(yellow_call_yellow(1, 2))))