lloydmeta / frunk

Funktional generic type-level programming in Rust: HList, Coproduct, Generic, LabelledGeneric, Validated, Monoid and friends.
https://beachape.com/frunk/
MIT License
1.24k stars 56 forks source link

Minimal superset of two Coproducts? #198

Closed rosefromthedead closed 2 years ago

rosefromthedead commented 2 years ago

In my library, I have a generic function that creates a superset of two coproducts, which come from its type arguments. Currently I'm using two CoproductEmbedder bounds, but since there are infinite coproduct types that satisfy any set of Embedder bounds, type inference fails, and users of the library have to specify the superset type themselves. This is bad on its own, but the function has 22 generic arguments, so the user ends up with quite a few underscores.

So, how can I get the compiler to infer the minimal superset (I suppose the union?) of some coproducts? I've been trying to make a new CoproductJoiner trait to help, but so far I've failed, and I suppose the maintainers here are likely better at this sort of thing than I am :D

ExpHP commented 2 years ago

If you're only ever using this with completely disjoint sets of types, this should be possible.

However, I presume you included the word "minimal" for a reason... and unfortunately, any attempt to join two coproducts without duplicates smells impossible. By which I mean, it sounds similar to numerous other things that have previously been found to be impossible to implement in frunk due to limitations of rust's trait system.

In particular:

then almost certainly, any attempt to make the first bullet work for most B will have parametricity over B, causing it to apply to all B, including B=A; which will conflict with the second bullet. Frunk's use of index types to disambiguate impls does allow it to overcome some problems of overlapping impls, but even this technique cannot be used to break parametricity.


22 generic arguments

Ahh-e-uh. Yikes!

I don't really have a nice alternative to recommend here.

Depending on your circumstances, perhaps one possible compromise is to use a single superset coproduct type throughout the higher-level parts of the application, to reduce the number of places where distinct type annotations must be maintained. (however, this also forfeits many of the benefits of Coproduct, and at some point one must ask themselves if they'd be better off with a regular enum).

Another possibility is that, if the simpler form of CoproductJoiner were added (one which just concatenates the coproducts, e.g. Coprod![A, B] | Coprod![A, C] == Coprod![A, B, A, C]), then maybe you could use that, and then at some point waaaaay down the line, you could convert your hastily-constructed Coprod![A, B, A, A, C, D, A, B, A, E, F, A] into a cleaner Coprod![A, B, C, D, E, F].

rosefromthedead commented 2 years ago

Thanks for the help. As it stands I don't think what I want is possible in Rust today. Honestly I'll probably just make a macro to allow the user to specify the type without also specifying the 21 underscores...

Yikes!

Understandable, but it's for a good cause.. eh.. I think. 9 of the arguments are frunk indices :P

It's possible that the concatenation idea might lead to something, but my intuition is that it won't, because I need to be able to embed the subsets in the superset without knowing the superset concretely. As far as I can see that's not possible without indices, which can't be inferred (or don't make sense) unless there are no duplicates.