Is there a way to expand type declarations?

I am trying to use the formless "Coproduct" for error type aggregation. The following is an attempt to isolate the problem I am currently having:

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 a result type in a custom fold without repeating myself? Ideally, I don’t want to declare F1234 the way I did, I want to declare it simply by creating a union of the two existing declaration types F12 and F34. This way, I don’t need to update the F1234 ad whenever I add another type of crash to any of these ads. I can declare type F1234 = F1.type: +: F2.type: +: F34, but I cannot declare type F1234 = F12: +: F34 due to the tail of CNil F12, which is dropped by extendBy operations.

+5
source share
2 answers

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 .

+5
source

Scala dependent types are always a little weird and bulky. Type-type functions are encoded as implicated with the results as type members, but with respect to the scala compiler, it's just a type member. So you need to do something like this:

 def custom(f: Either[F12, F34])(implicit er: ExtendRight[F12, F34]): er.Out = f.fold(_.extendRightBy[F34], _.extendLeftBy[F12]) 

Unfortunately, this will not work, because the compiler cannot say that the output of extendLeftBy[F12] is the same type. We know that the two types will always be the same, but the compiler does not, so we must require a witness (which is actually always present). Sort of:

 def custom(f: Either[F12, F34])(implicit er: ExtendRight[F12, F34], el: ExtendLeft[F34, F12])(implicit w: er.Out =:= el.Out): er.Out = f.fold(_.extendRightBy[F34], w(_.extendLeftBy[F12])) 

Unfortunately, even this does not work, because our type parameters are not allowed to depend on type parameters from the same list, and we can only have one implicit list. Therefore, we must "raise" these types instead of type parameters:

 def custom[ERO, ELO](f: Either[F12, F34])( implicit er: ExtendRight[F12, F34]{type Out = ERO}, el: ExtendLeft[F34, F12]{type Out = ELO}, w: ELO =:= ERO): ELO = f.fold(_.extendRightBy[F34], w(_.extendLeftBy[F12])) 

As I said, it is cumbersome, but it should work. ( ExtendRight and ExtendLeft - types used by the extendRightBy and extendLeftBy - any similarly dependent function will probably have similar "helper" types).

+3
source

All Articles