effect icon indicating copy to clipboard operation
effect copied to clipboard

Support for Async Performers in Effect Library

Open giuliohome opened this issue 1 year ago • 4 comments

Hello,

I’m currently implementing an async performer, starting from a synchronous version. In the synchronous setup, I have:

result = sync_perform(mock_dispatcher, main_sequence(logger))

This approach uses a pure logic sequence for managing effects in main_sequence, which can be found here.

For the async version, I have created a custom async_perform function to handle asynchronous performers, defined here.

Usage

In the async main, the call looks like this:

result = await async_perform(mock_dispatcher, main_sequence(logger))

Here’s an example of an async performer:

# ...
    async def mock_create_request_performer(dispatcher, intent):
        logger.debug(f"Mock: Creating request... {intent.payload} with token {token}")
        await asyncio.sleep(1)
# ...
    return TypeDispatcher({
        CreateRequest: mock_create_request_performer,
# ...

Look at my feat/async branch for the whole sample.

Would this be considered the recommended approach for handling async performers, or are there any improvements or best practices that you’d suggest?

Thank you!

giuliohome avatar Oct 30 '24 20:10 giuliohome

@giuliohome just so you know, this library is effectively abandoned, since I haven't touched it or even really written any Python in several years.

You should look at the way txeffect implements its txeffect.perform method. Ideally, this would be ported to python's built-in async system and included directly in the main effect library.

Your implementation seems to be reimplementing all of effect.perform (which is separate from effect.sync_perform), but you should just be able to invoke it instead of reimplementing it. There are some situations that effect.perform will automatically solve for you like avoiding extraneous stack usage by way of a trampoline.

Another intentional design is that performers get decorated either in a @sync_performer or a @deferred_performer, which again I would port to a @async_performer decorator that does the same thing. Maybe this could be rethought so that the decorator isn't needed, but that's the idiom that effect goes with.

radix avatar Nov 04 '24 19:11 radix

Yes, thank you very much. I understand what you suggest, you're right, indeed I tried to do that initially, but I failed somewhere and resorted to the full implementation, because I was in a hurry. I will try again soon, thank you for the kind and prompt reply.

giuliohome avatar Nov 04 '24 19:11 giuliohome

I've reviewed it again, but unfortunately, it doesn't seem possible to do it that way. In a word, the core issue here is the async 'color' problem, which fundamentally sets it apart from a pure callback

giuliohome avatar Nov 04 '24 20:11 giuliohome

If both the trampoline and the bouncer are made async, then this async performer will function as expected

def async_performer(f):
    @wraps(f)
    async def async_wrapper(*args, **kwargs):
        box = args[-1]
        pass_args = args[:-1]
        try:
            print(f"Performing effect: {f} in async_performer!!!")
            result = await f(*pass_args, **kwargs)
            await box.succeed(result)
        except Exception as e:
            box.fail(e)

    return async_wrapper

I will fork this library to show the async version.

giuliohome avatar Nov 05 '24 08:11 giuliohome