-
Notifications
You must be signed in to change notification settings - Fork 66
Description
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?