The situation is not so bad as the lmm answer suggests, partly because Shapeless provides an ExtendBy class that packs ExtendLeftBy and ExtendRightBy . Therefore, if you really need a return type for custom , which you did not calculate yourself and did not ExtendBy out manually, you can use ExtendBy :
import shapeless._, ops.coproduct.ExtendBy 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 def custom(f: Either[F12, F34])(implicit ext: ExtendBy[F12, F34]): ext.Out = f.fold(ext.right(_), ext.left(_))
Even if you needed to use ExtendLeftBy and ExtendRightBy , you could convince the compiler that they have the same output type a little more cleanly with Aux types and one parameter of a common type. So instead (full working version of lmm code):
import ops.coproduct.{ ExtendLeftBy, ExtendRightBy } def custom[ERO, ELO](f: Either[F12, F34])(implicit el: ExtendRightBy[F12, F34] { type Out = ELO }, er: ExtendLeftBy[F12, F34] { type Out = ERO }, w: ELO =:= ERO ): ERO = f.fold(l => w(el(l)), er(_))
You just write this:
def custom[Out <: Coproduct](f: Either[F12, F34])(implicit extL: ExtendRightBy.Aux[F12, F34, Out], extR: ExtendLeftBy.Aux[F12, F34, Out] ): Out = f.fold(extL(_), extR(_))
In most cases, if you know the static input types, you simply write the return type yourself and generally skip the implicit parameter. Implicit evidence is needed only when you are working with generic types, for example:
def custom[A <: Coproduct, B <: Coproduct](f: Either[A, B])(implicit ext: ExtendBy[A, B] ): ext.Out = f.fold(ext.right(_), ext.left(_))
This works for any two copies, not just F12 and F34 .