given:
sealed trait Data
final case class Foo() extends Data
final case class Bar() extends Data
final case class TimestampedData[A <: Data](data: A, timestamp: Long)
Is there a succint way to generate, for example, a Generic.Aux
that will take a
(A, Long)
where A <: Data
and out this Coproduct
:
TimestampedData[Foo] : : TimestampedData[Bar] : : CNil
(Generic.Aux[(A, Long), TimestampedData[Foo] : : TimestampedData[Bar] : : CNil]
)
?
Unfortunately, since I don't know much generic programming and because of the lack of resources, I haven't tried much. I'm not even sure how to approach this problem.
Thanks
CodePudding user response:
You can try a method with PartiallyApplied
pattern
import shapeless.{Coproduct, DepFn2, Generic, HList}
import shapeless.ops.coproduct.{Inject, ToHList}
import shapeless.ops.hlist.{Mapped, ToCoproduct}
def toTimestamped[A <: Data] = new PartiallyApplied[A]
class PartiallyApplied[A <: Data] {
def apply[C <: Coproduct,
L <: HList,
L1 <: HList,
C1 <: Coproduct](data: A, timestamp: Long)(implicit
generic: Generic.Aux[Data, C],
toHList: ToHList.Aux[C, L],
mapped: Mapped.Aux[L, λ[A => TimestampedData[A with Data]], L1],
toCoproduct: ToCoproduct.Aux[L1, C1],
inject: Inject[C1, TimestampedData[A]],
): C1 = inject(TimestampedData[A](data, timestamp))
}
val x = toTimestamped(Foo(), 1L) // Inr(Inl(TimestampedData(Foo(),1)))
val y = toTimestamped(Bar(), 1L) // Inl(TimestampedData(Bar(),1))
type Coprod = TimestampedData[Bar] : : TimestampedData[Foo] : : CNil
x: Coprod // compiles
y: Coprod // compiles
or a typeclass 1 2 3 4 5 (generally, a more flexible solution than a method although now there seem to be no advantages over a method because there is the only instance of the type class)
trait ToTimestamped[A <: Data] extends DepFn2[A, Long] {
type Out <: Coproduct
}
object ToTimestamped {
type Aux[A <: Data, Out0 <: Coproduct] = ToTimestamped[A] { type Out = Out0 }
def instance[A <: Data, Out0 <: Coproduct](f: (A, Long) => Out0): Aux[A, Out0] =
new ToTimestamped[A] {
override type Out = Out0
override def apply(data: A, timestamp: Long): Out0 = f(data, timestamp)
}
implicit def mkToTimestamped[A <: Data,
C <: Coproduct,
L <: HList,
L1 <: HList,
C1 <: Coproduct](implicit
generic: Generic.Aux[Data, C],
toHList: ToHList.Aux[C, L],
mapped: Mapped.Aux[L, λ[A => TimestampedData[A with Data]], L1],
toCoproduct: ToCoproduct.Aux[L1, C1],
inject: Inject[C1, TimestampedData[A]],
): Aux[A, C1] =
instance((data, timestamp) => inject(TimestampedData[A](data, timestamp)))
}
def toTimestamped[A <: Data](data: A, timestamp: Long)(implicit
toTimestampedInst: ToTimestamped[A]
): toTimestampedInst.Out = toTimestampedInst(data, timestamp)
Testing:
val x = toTimestamped(Foo(), 1L) // Inr(Inl(TimestampedData(Foo(),1)))
val y = toTimestamped(Bar(), 1L) // Inl(TimestampedData(Bar(),1))
type Coprod = TimestampedData[Bar] : : TimestampedData[Foo] : : CNil
implicitly[ToTimestamped.Aux[Foo, Coprod]] // compiles
x: Coprod // compiles
y: Coprod // compiles
In Shapeless there is Mapped
for HList
but not Coproduct
, so I had to transform on type level Coproduct
to HList
and back.
λ[A => ...]
is kind-projector syntax. Mapped
accepts a type constructor F[_]
but TimestampedData
is upper-bounded F[_ <: Data]
, so I had to use a type lambda with intersection type (with
).