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.
To extend types in TypeScript, we can use the extends keyword. to create the UserEvent interface that extends the Event type. We extend it by adding the string UserId field in UserEvent and inheriting the rest from Event .
An interface can be extended by other interfaces. In other words, an interface can inherit from other interface. Typescript allows an interface to inherit from multiple interfaces. Use the extends keyword to implement inheritance among interfaces.
Extending Interfaces in TypeScript # Use the extends keyword to extend interfaces in TypeScript, e.g. interface Dog extends Animal {age: number;} . The extends keyword allows us to copy the members from other named types and add new members to the final, more generic interface. Copied!
The situation isn't quite as bad as lmm's answer suggests, in part because Shapeless provides a ExtendBy
type class that packages up ExtendLeftBy
and ExtendRightBy
. So if you really wanted a return type for custom
that you didn't compute yourself and write out by hand, you could 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 did need to use ExtendLeftBy
and ExtendRightBy
directly, you could convince the compiler that they have the same output type a little more cleanly with Aux
types and a single shared type parameter. So instead of this (a complete working version of lmm's 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 would 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 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:
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 coproducts, not just F12
and F34
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With