Skip to content

Add full monad transformer support for monadic composition of CGP handlers#129

Merged
soareschen merged 21 commits intomainfrom
monadic-bind
Jul 18, 2025
Merged

Add full monad transformer support for monadic composition of CGP handlers#129
soareschen merged 21 commits intomainfrom
monadic-bind

Conversation

@soareschen
Copy link
Collaborator

@soareschen soareschen commented Jul 18, 2025

Summary

This is a follow up improvement over #127 to provide full monadic transformer support on handling monadic composition on CGP providers such as Computer and `Handlers.

Compared to the previous PR, we can now make use of a monad transformer stack to compose handlers that return results wrapped in nested monads.

Example

An example use of this is the OkMonadicTrans<ErrMonadic> monad, which can extract a value T from a nested result type Result<Result<E1, T>, T>, where the inner success value is placed in Err instead of Ok. This use case is particularly useful to implement the extensible visitor pattern, where the extraction of a variant returns the remainder in Err, while the successful extraction in Ok is short circuited.

#[cgp_computer]
pub fn return_ok_ok(_value: u8) -> Result<Result<(), u8>, &'static str> {
    Ok(Ok(()))
}

#[cgp_computer]
pub fn return_ok_err(value: u8) -> Result<Result<(), u8>, &'static str> {
    Ok(Err(value))
}

#[cgp_computer]
pub fn return_err(_value: u8) -> Result<Result<(), u8>, &'static str> {
    Err("error")
}

#[test]
pub fn test_ok_err_monadic_trans() {
    let context = ErrorOnly::<&'static str>::default();
    let code = PhantomData::<()>;

    assert_eq!(
        PipeMonadic::<OkMonadicTrans<ErrMonadic>, Product![ReturnOkErr, ReturnOkErr, ReturnOkErr]>::compute(
            &context, code, 1
        ),
        Ok(Err(1)),
    );

    assert_eq!(
        PipeMonadic::<OkMonadicTrans<ErrMonadic>, Product![ReturnOkErr, ReturnOkOk, ReturnOkErr]>::compute(
            &context, code, 1
        ),
        Ok(Ok(())),
    );

    assert_eq!(
        PipeMonadic::<OkMonadicTrans<ErrMonadic>, Product![ReturnErr, ReturnOkOk, ReturnOkErr]>::compute(
            &context, code, 1
        ),
        Err("error"),
    );
}

Implementation Details

Behind the scene, instead of composing two handlers directly, we define a MonadicBind to lift the inputs of a handler into a monad. It has a Haskell signature that looks like:

class MonadicBind b m where
    bind_pure :: (a -> m b) -> m a -> m b

    bind_async :: (a -> Future (m a)) -> m a -> Future (m b)

That is, the argument order of the CGP bind is the inverse of the usual ordering of (>>=):

(>>=) :: m a -> (a -> m b) -> m b

The main implication is that the CGP monadic bind can work directly with a Computer that is in the form a -> m b, and produce a new Computer instance that is in the form m a -> m b.

Another main difference of CGP's monad and Haskell's monad is that the monadic operation is defined outside of the involved type. For example, both OkMonadic and ErrMonadic can be used to perform monadic bind on the Result type. This design is intentional, as we also want to eventually support monadic operations on opaque types that implement monadic traits, such as impl Future or impl Iterator. Since these opaque types are all distinct types, we have to make the monadic bind to support arbitrary types that implement the specific traits that we are interested in, without explicitly wrapping them in a monadic type.

The MonadicBind implementation in CGP also requires explicit support on the Handler component, which has an async function that returns a Future. Although the Future itself is monadic, it is currently not possible to name that opaque future as type due to Associated type position impl trait (ATPIT) not yet being stabilized. In other words, it is currently not possible to "downcast" a Handler into a Computer, because we can't name the returned impl Future as the associated Output type in Computer.

@soareschen soareschen merged commit ddbc1eb into main Jul 18, 2025
5 checks passed
@soareschen soareschen deleted the monadic-bind branch July 18, 2025 11:49
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.

1 participant