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
).
TimestampedData[Foo] :+: TimestampedData[Bar] :+: CNil
, you would define a type classTypeClass[A]
, then provide an instance of type classTypeClass[A]
- which in your case could be generated with the help ofGeneric[Data]
,A =:= Data
- and pass it toimplicit def forTimestampedData[A](implicit a: TypeClass[A]): TypeClass[TimestampedData[A]] = /* your implementation */
. – StevestevedoreData
toTimestampedData
? Because I can already derive aGeneric[Data]
easily (which gets meFoo :+: Bar :+: CNil
). Then I map those toTimestampedData
? What about the timestamp? – VitiatedTimestampedData[A]
using behavior forA
, then behavior forData
can be derived usingCoproduct
and used inTimestampedData[A]
behavior. If you want o derive behavior forTimestampedData[A]
as well, you can use derivation forHList
. But it all requires you to know what behavior you actually want and how you want it composed. – Stevestevedore