Skip to content

How to reverse a Coproduct::subset() after mapping both halves? #206

@BGR360

Description

@BGR360

I have an interesting challenge:

I need to map some coproduct type Coprod!(A, B, C, D) to Coprod!(T, U, V, W). However, I have to be able to perform this mapping in two arbitrarily-partitioned steps.

So I need to be able to, say, break the input into (A, B) and (C, D), then map them to (T, U) and (V, W) separately, then embed one of those into (T, U, V, W).

So basically this:

pub fn challenge<
    Input,
    Output,
    Subset,
    SubsetMapper,
    SubsetMapperOutput,
    RemainderMapper,
    RemainderMapperOutput,
    I,
    J,
    K,
>(
    input: Input,
    subset_mapper: SubsetMapper,
    remainder_mapper: RemainderMapper,
) -> Output
where
    Input: CoproductSubsetter<Subset, I>,

    Subset: CoproductMappable<SubsetMapper, Output = SubsetMapperOutput>,
    SubsetMapperOutput: CoproductEmbedder<Output, J>,

    Input::Remainder: CoproductMappable<RemainderMapper, Output = RemainderMapperOutput>,
    RemainderMapperOutput: CoproductEmbedder<Output, K>,
{
    match input.subset() {
        Ok(subset) => {
            let subset_output = subset.map(subset_mapper);
            subset_output.embed()
        }
        Err(remainder) => {
            let remainder_output = remainder.map(remainder_mapper);
            remainder_output.embed()
        }
    }
}
Why

This is for the effect system I'm working on. When an effectful computation a calls another effectful computation b, it has the option to intercept any subset of the effects that b raises and re-raise the rest to the caller. So in some made-up language:

effect A -> T;
effect B -> U;
effect C -> V;
effect D -> W;

fn b() raises A | B | C | D {
    let t = raise A;
    let u = raise B;
    let v = raise C;
    let w = raise D;
}

fn a() raises C | D {
    try {
        b()
    } with {
        A => resume T,
        B => resume U,
    } /* implicitly re-raises C and D to be handled by the caller */
}

In Rust the effectful computations are implemented as generators where each raised effect does a yield of a Coprod!(<the effects>) and awaits a message of Coprod!(<the effect outputs>):

// `let t = raise A;`
let t: T = {
    let (send, recv) = channel();
    yield (Coprod!(A, B, C, D)::inject(A), send);
    let resolved: Coprod!(T, U, V, W) = recv.recv();
    resolved.uninject().unwrap()
}

This actually works for the example I gave. This compiles just fine:

struct A;
struct B;
struct C;
struct D;

struct T;
struct U;
struct V;
struct W;

type Input = Coprod!(A, B, C, D);
type Output = Coprod!(T, U, V, W);

type Subset = Coprod!(A, B);

let subset_mapper = hlist![|A| T, |B| U];
let remainder_mapper = hlist![|C| V, |D| W];

let output: Output = challenge::<_, _, Subset, _, _, _, _, _, _, _>(
    Input::inject(A),
    subset_mapper,
    remainder_mapper,
);
assert!(output.get::<T, _>().is_some());

The real challenge is that T, U, V, W are not guaranteed to be unique types. If I make the following changes:

type Output = Coprod!(T, U, T, U);

let remainder_mapper = hlist![|C| T, |D| U];

Then it fails to perform inference on the J and K indices due to multiple applicable impls:

error[E0283]: type annotations needed
   --> src/private.rs:262:26
    |
262 |     let output: Output = challenge::<_, _, Subset, _, _, _, _, _, _, _>(
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `J` declared on the function `challenge`
    |

So it seems I have to somehow remember the indices from the subset operation. This wouldn't be so hard if all I need to do is re-embed the Ok() half. I think I could make some wrapper type that remembers the indices I of the original subset() call and uses those to embed the partial output into the final output.

But the tricky bit is that I also need to be able to re-embed the remainder half into the output type.

I've been staring at the CoproductSubsetter code on my screen for a couple of hours now and can't figure out how one might go about doing this. Is it possible to somehow get the "inverse" of the indices inferred for the subset() call?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions