I'm trying to use shapeless' Coproduct for error type aggregation. The following is an attempt to isolate the problem I currently have:
import shapeless._
case object F1
case object F2
type F12 = F1.type :+: F2.type :+: CNil
case object F3
case object F4
type F34 = F3.type :+: F4.type :+: CNil
type F1234 = F1.type :+: F2.type :+: F3.type :+: F4.type :+: CNil
def custom(f: Either[F12, F34]): F1234 = // how can I declare the resulting type?
f.fold(_.extendRightBy[F34], _.extendLeftBy[F12])
object F1234Handler extends Poly1 {
implicit def caseF1 = at[F1.type](_ => "got F1")
implicit def caseF2 = at[F2.type](_ => "got F2")
implicit def caseF3 = at[F3.type](_ => "got F3")
implicit def caseF4 = at[F4.type](_ => "got F4")
}
custom(Left(Coproduct[F12](F2))).fold(F1234Handler) // got F2
How can I declare the result type in the custom fold without having to repeat myself? Ideally I don't want to declare F1234 in the way I did, I want to declare it by just making a union of the two existing type declarations, F12 and F34. This way I don't need to update the F1234 declaration whenever I add another failure type to either of these declarations. I can declare type F1234 = F1.type :+: F2.type :+: F34 but I can't declare type F1234 = F12 :+: F34 due to the CNil tail of the F12, which gets dropped by the extendBy operations.
The situation isn't quite as bad as lmm's answer suggests, in part because Shapeless provides a
ExtendBy
type class that packages upExtendLeftBy
andExtendRightBy
. So if you really wanted a return type forcustom
that you didn't compute yourself and write out by hand, you could useExtendBy
:Even if you did need to use
ExtendLeftBy
andExtendRightBy
directly, you could convince the compiler that they have the same output type a little more cleanly withAux
types and a single shared type parameter. So instead of this (a complete working version of lmm's code):You would just write this:
In most cases if you know the input types statically, though, you'd just write out the return type yourself and skip the implicit parameter business altogether. The implicit evidence is only necessary when you're working with generic types, like this:
This works for any two coproducts, not just
F12
andF34
.