Converting case class to another recursively structural identical case class - scala

I am trying to use Shapeless to convert case class like these:
case class A(int: Int, str: String)
case class B(a: A, str: String)
case class AnotherA(int: Int, str: String)
case class AnotherB(a: AnotherA, str: String)
Using Generic I can easily convert between A and AnotherA, but not for B and AnotherB because (of course) the a member is of different type.
I think I can convert case class into a nested HList following this example, but how do I convert the nested HList back to case class or there is a more straightforward way to do it ?

Here's a possible implementation of a DeepHUnlister which recursively replaces the sub-Hlists within the generic representation (nested HList) with their respective case class instances:
trait DeepHUnlister[L <: HList, R <: HList] extends (L => R)
object DeepHUnlister {
implicit object hnil extends DeepHUnlister[HNil, HNil] {
def apply(r: HNil) = HNil
}
implicit def headList[H <: HList, T <: HList, HR <: HList, TR <: HList, A](
implicit
gen: Generic.Aux[A, HR],
dhh: Lazy[DeepHUnlister[H, HR]],
dht: Lazy[DeepHUnlister[T, TR]]):
DeepHUnlister[H :: T, A :: TR] =
new DeepHUnlister[H :: T, A :: TR] {
def apply(r: H :: T) = gen.from(dhh.value(r.head)) :: dht.value(r.tail)
}
implicit def headAtomic[H, T <: HList, TR <: HList](
implicit dht: Lazy[DeepHUnlister[T, TR]]):
DeepHUnlister[H :: T, H :: TR] =
new DeepHUnlister[H :: T, H :: TR] {
def apply(r: H :: T) = r.head :: dht.value(r.tail)
}
def apply[T <: HList, R <: HList](implicit du: DeepHUnlister[T, R]):
DeepHUnlister[T, R] = du
}
Example:
case class A(int: Int, str: String)
case class B(a: A, str: String)
case class C(b: B, d: Double)
case class A1(int: Int, str: String)
case class B1(a: A1, str: String)
case class C1(a: B1, d: Double)
type ARepr = Int :: String :: HNil
type BRepr = ARepr :: String :: HNil
type CRepr = BRepr :: Double :: HNil
val c = C(B(A(1, "a"), "b"), 1.0)
val lister = DeepHLister[C :: HNil]
val repr = lister(c :: HNil)
println(repr)
val unlister = DeepHUnlister[CRepr :: HNil, C1 :: HNil]
val c1 = unlister(repr).head
// c1 = C1(B1(A1(1,a),b),1.0)
Maybe it's possible to enhance this solution to avoid passing the representation type as parameter to the unlister.
Update
Here's a version that omits the source type, but unfortunately requires a cast:
trait DepInvFn1[T] {
type In
def apply(i: In): T
}
trait DeepHUnlister[R <: HList] extends DepInvFn1[R] { type In <: HList }
trait DeepHUnlisterLP {
type Aux[L <: HList, R <: HList] = DeepHUnlister[R] { type In = L }
implicit def headAtomic[H, TR <: HList](
implicit dt: Lazy[DeepHUnlister[TR]]):
Aux[H :: dt.value.In, H :: TR] =
new DeepHUnlister[H :: TR] {
type In = H :: dt.value.In
def apply(r: H :: dt.value.In) = r.head :: dt.value(r.tail)
}
}
object DeepHUnlister extends DeepHUnlisterLP {
implicit object hnil extends DeepHUnlister[HNil] {
type In = HNil
def apply(r: HNil) = HNil
}
implicit def headList[HR <: HList, TR <: HList, A](
implicit
gen: Generic.Aux[A, HR],
dh: Lazy[DeepHUnlister[HR]],
dt: Lazy[DeepHUnlister[TR]]):
Aux[dh.value.In :: dt.value.In, A :: TR] =
new DeepHUnlister[A :: TR] {
type In = dh.value.In :: dt.value.In
def apply(r: dh.value.In :: dt.value.In) = gen.from(dh.value(r.head)) :: dt.value(r.tail)
}
def apply[R <: HList](implicit du: DeepHUnlister[R]): DeepHUnlister[R] = du
}
Usage:
val unlister = DeepHUnlister[C1 :: HNil]
val c1 = unlister(repr.asInstanceOf[unlister.In]).head
// c1 = C1(B1(A1(1,a),b),1.0)

Related

Automatically transfer HList into a Record

My goal is to automatically transfer a HList into a Record on demand.
Note that the below code is written in Scala 2.13 and uses singleton types instead of Symbols.
import shapeless._
import shapeless.record._
import shapeless.labelled._
type BestBeforeDate = Record.`"day" -> Option[Int], "month" -> Option[Int], "year" -> Int`.T
object PolyToField extends Poly1 {
implicit def mapA[A, K]: Case.Aux[A, FieldType[K, A]] = at[A](a => field[K][A](a))
}
val x: BestBeforeDate = (None :: None :: 12 :: HNil)
.map(PolyToField)
I get this error:
type mismatch;
found : None.type with shapeless.labelled.KeyTag[Nothing,None.type] :: None.type with shapeless.labelled.KeyTag[Nothing,None.type] :: Int with shapeless.labelled.KeyTag[Nothing,Int] :: shapeless.HNil
required: emergencymanager.commons.data.package.BestBeforeDate
(which expands to) Option[Int] with shapeless.labelled.KeyTag[String("day"),Option[Int]] :: Option[Int] with shapeless.labelled.KeyTag[String("month"),Option[Int]] :: Int with shapeless.labelled.KeyTag[String("year"),Int] :: shapeless.HNil
It seems like the second type parameter of the Poly1 function is inferred to Nothing instead of what I need.
How can I achieve this?
Firstly, you should specify with type ascription that None has type Option[...] (that's the reason why in Cats there are none[...] and .some, see also, item 9). Or use Option.empty[...].
Secondly, you should use mapper with return type rather than standard shapeless.ops.hlist.Mapper
trait MapperWithReturnType[HF, Out <: HList] extends Serializable {
type In <: HList
def apply(t: In): Out
}
object MapperWithReturnType {
type Aux[HF, Out <: HList, In0 <: HList] =
MapperWithReturnType[HF, Out] { type In = In0 }
def instance[HF, Out <: HList, In0 <: HList](f: In0 => Out): Aux[HF, Out, In0] =
new MapperWithReturnType[HF, Out] {
override type In = In0
override def apply(t: In0): Out = f(t)
}
implicit def hnilMapper[HF <: Poly]: Aux[HF, HNil, HNil] = instance(_ => HNil)
implicit def hconsMapper[HF <: Poly, InH, InT <: HList, OutH, OutT <: HList](implicit
hc : poly.Case1.Aux[HF, InH, OutH],
mt : Aux[HF, OutT, InT]
): Aux[HF, OutH :: OutT, InH :: InT] = instance(l => hc(l.head) :: mt(l.tail))
}
implicit final class HListOps[L <: HList](l : L) extends Serializable {
def mapWithReturnType[Out <: HList](f: Poly)(implicit
mapper: MapperWithReturnType.Aux[f.type, Out, L]
): Out = mapper(l)
}
val x = ((None: Option[Int]) :: (None: Option[Int]) :: 12 :: HNil)
.mapWithReturnType[BestBeforeDate](PolyToField)
// val x = (Option.empty[Int] :: Option.empty[Int] :: 12 :: HNil)
// .mapWithReturnType[BestBeforeDate](PolyToField)
x: BestBeforeDate

Shapeless and annotations

I would like to have some function applied to fields in a case class, that are annotated with MyAnnotation. The idea is to transform type T into its generic representation, extract annotations, zip, fold right (or left) to reconstruct a generic representation and finally get back to type T. I followed the answer provided here and this gist.
I'm using scala 2.11.12 and shapeless 2.3.3.
Hereafter is my code:
import shapeless._
import shapeless.ops.hlist._
case class MyAnnotation(func: String) extends scala.annotation.StaticAnnotation
trait Modifier[T] {
def modify(t: T): T
}
object Modifier {
def apply[A: Modifier]: Modifier[A] = implicitly[Modifier[A]]
def create[T](func: T => T): Modifier[T] = new Modifier[T] { override def modify(t: T): T = func(t) }
private def id[T](t: T) = t
implicit val stringModifier: Modifier[String] = create(id)
implicit val booleanModifier: Modifier[Boolean] = create(id)
implicit val byteModifier: Modifier[Byte] = create(id)
implicit val charModifier: Modifier[Char] = create(id)
implicit val doubleModifier: Modifier[Double] = create(id)
implicit val floatModifier: Modifier[Float] = create(id)
implicit val intModifier: Modifier[Int] = create(id)
implicit val longModifier: Modifier[Long] = create(id)
implicit val shortModifier: Modifier[Short] = create(id)
implicit val hnilModifier: Modifier[HNil] = create(id)
implicit def hlistModifier[H, T <: HList, AL <: HList](
implicit
hser: Lazy[Modifier[H]],
tser: Modifier[T]
): Modifier[H :: T] = new Modifier[H :: T] {
override def modify(ht: H :: T): H :: T = {
ht match {
case h :: t =>
hser.value.modify(h) :: tser.modify(t)
}
}
}
implicit val cnilModifier: Modifier[CNil] = create(id)
implicit def coproductModifier[L, R <: Coproduct](
implicit
lser: Lazy[Modifier[L]],
rser: Modifier[R]
): Modifier[L :+: R] = new Modifier[L :+: R] {
override def modify(t: L :+: R): L :+: R = t match {
case Inl(l) => Inl(lser.value.modify(l))
case Inr(r) => Inr(rser.modify(r))
}
}
object Collector extends Poly2 {
implicit def myCase[ACC <: HList, E] = at[(E, Option[MyAnnotation]), ACC] {
case ((e, None), acc) => e :: acc
case ((e, Some(MyAnnotation(func))), acc) => {
println(func)
e :: acc
}
}
}
implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](
implicit
gen: Generic.Aux[T, HL],
ser: Lazy[Modifier[HL]],
annots: Annotations.Aux[MyAnnotation, T, AL],
zip: Zip.Aux[HL :: AL :: HNil, ZL],
rightFolder: RightFolder[ZL, HNil.type, Collector.type]
): Modifier[T] = new Modifier[T] {
override def modify(t: T): T = {
val generic = gen.to(t)
println(generic)
val annotations = annots()
println(annotations)
val zipped = zip(generic :: annotations :: HNil)
println(zipped)
val modified = zipped.foldRight(HNil)(Collector)
println(modified)
val typed = gen.from(generic) // temporary
typed
}
}
}
The code above compiles. However, when instanciating a Modifier in a test:
case class Test(a: String, #MyAnnotation("sha1") b: String)
val test = Test("A", "B")
val modifier: Modifier[Test] = implicitly
the test file does not compile and give the following error:
[error] ambiguous implicit values:
[error] both value StringCanBuildFrom in object Predef of type =>
scala.collection.generic.CanBuildFrom[String,Char,String]
[error] and method $conforms in object Predef of type [A]=> <:<[A,A]
[error] match expected type T
[error] val ser1: Modifier[Test] = implicitly
The problem seems to come from the right folder definition: when removing rightFolder from the list of implicits in genericModifier, then it works:
implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](
implicit
gen: Generic.Aux[T, HL],
ser: Lazy[Modifier[HL]],
annots: Annotations.Aux[MyAnnotation, T, AL],
zip: Zip.Aux[HL :: AL :: HNil, ZL]/*,
rightFolder: RightFolder[ZL, HNil.type, Collector.type]*/
): Modifier[T] = new Modifier[T] {
override def modify(t: T): T = {
val generic = gen.to(t)
println(generic)
val annotations = annots()
println(annotations)
val zipped = zip(generic :: annotations :: HNil)
println(zipped)
/*val modified = zipped.foldRight(HNil)(Collector)
println(modified)*/
val typed = gen.from(generic) // temporary
typed
}
}
What is wrong?
There are several mistakes in your code:
defining Poly just for Option is too rough (pattern matching is performed at runtime and compiler should know definitions for Some and None at compile time)
HNil should be instead of HNil.type and HNil : HNil instead of HNil (types HNil and HNil.type are different)
compiler doesn't know that RightFolder actually returns the original HList type, so you should use RightFolder.Aux type.
Correct code is
import shapeless.ops.hlist.{RightFolder, Zip}
import shapeless.{::, Annotations, Generic, HList, HNil, Lazy, Poly2}
import scala.annotation.StaticAnnotation
object App {
case class MyAnnotation(func: String) extends StaticAnnotation
object Collector extends Poly2 {
// implicit def myCase[ACC <: HList, E] = at[(E, Option[PII]), ACC] {
// case ((e, None), acc) => e :: acc
// case ((e, Some(MyAnnotation(func))), acc) => {
// println(func)
// e :: acc
// }
// }
implicit def someCase[ACC <: HList, E]: Case.Aux[(E, Some[MyAnnotation]), ACC, E :: ACC] = at {
case ((e, Some(MyAnnotation(func))), acc) =>
println(func)
e :: acc
}
implicit def noneCase[ACC <: HList, E]: Case.Aux[(E, None.type), ACC, E :: ACC] = at {
case ((e, None), acc) => e :: acc
}
}
trait Modifier[T] {
def modify(t: T): T
}
implicit def hListModifier[HL <: HList]: Modifier[HL] = identity(_)
// added as an example, you should replace this with your Modifier for HList
implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](implicit
gen: Generic.Aux[T, HL],
ser: Lazy[Modifier[HL]],
annots: Annotations.Aux[MyAnnotation, T, AL],
zip: Zip.Aux[HL :: AL :: HNil, ZL],
rightFolder: RightFolder.Aux[ZL, HNil/*.type*/, Collector.type, HL /*added*/]
): Modifier[T] = new Modifier[T] {
override def modify(t: T): T = {
val generic = gen.to(t)
println(generic)
val annotations = annots()
println(annotations)
val zipped = zip(generic :: annotations :: HNil)
println(zipped)
val modified = zipped.foldRight(HNil : HNil /*added*/)(Collector)
println(modified)
val typed = gen.from(modified)
typed
}
}
case class Test(a: String, #MyAnnotation("sha1") b: String)
val test = Test("A", "B")
val modifier: Modifier[Test] = implicitly[Modifier[Test]]
def main(args: Array[String]): Unit = {
val test1 = modifier.modify(test) // prints "sha1"
println(test1) // Test(A,B)
}
}

HList transformation Out type

I have created a wrapper on top on HList that can append an HLists. It is defined as follows:
class HListWrapper[L <: HList](val hl: L) {
def append[V, Out <: HList](k: Witness, v: V)(implicit updater: Updater.Aux[L, FieldType[k.T, V], Out],
lk: LacksKey[L, k.T]): HListWrapper[Out] = {
new HListWrapper(updater(hl, field[k.T](v)))
}
}
object HListWrapper {
def apply[P <: Product, L <: HList](p: P)(implicit gen: LabelledGeneric.Aux[P, L]) =
new HListWrapper(gen.to(p))
}
It used like this:
case class CC(i: Int, ii: Int)
val cc = CC(100, 1000)
val hl = HListWrapper(cc)
hl.append('iii, 10000)
But when I try to put the HListWrapper inside another function to capture the type of Out, the compiler cannot seem to resolve the final type of the transformation (fails with type mismatch error):
def cctohlist[Out <: HList]: CC => HListWrapper[Out] = {
m => {
val hl = HListWrapper(m)
hl.append('iii, 10000)
}
}
The primary reason to create the cctohlist method is to get the type of the HList after append. Is this possible to achieve?
The following code works:
def cctohlist: CC => HListWrapper[Record.`'i -> Int, 'ii -> Int, 'iii -> Int`.T] = {
m => {
val hl = HListWrapper(m)
hl.append('iii, 10000)
}
}
When you write
def cctohlist[Out <: HList]: CC => HListWrapper[Out] = ???
this means I can apply cctohlist[Int :: String :: Boolean :: HNil] and have CC => HListWrapper[Int :: String :: Boolean :: HNil] or I can apply cctohlist[AnyVal :: AnyRef :: Any :: HNil] and have CC => HListWrapper[AnyVal :: AnyRef :: Any :: HNil] etc. This is obviously not the case.
This is generic one:
def cctohlist[P <: Product, L <: HList, Out <: HList](m: P)(implicit
gen: LabelledGeneric.Aux[P, L],
updater: Updater.Aux[L, FieldType[Witness.`'iii`.T, Int], Out],
lk: LacksKey[L, Witness.`'iii`.T]
): HListWrapper[Out] = {
val hl = HListWrapper(m)
hl.append('iii, 10000)
}
cctohlist(cc)

Zip generic HList with static Nat HList

I'm searching a way to zip two HList together. The first one is generated from a case class converted in its generic representation, and the second one is defined manually as an HList of Nat.
As a result, I expect a tuple (or 2-members HList) with one field from the case class, and the Nat associated.
The goal is to create a "customizable" ZipWithIndex.
def derive[A, I <: HList, R <: HList, Z <: HList](implicit
gen: Generic.Aux[A, R],
zipper: Zip.Aux[R, I, Z],
enc: Lazy[Encoder[Z]])(a: A): Deriver[A] = {
val genRepr = gen.to(A)
val zipped = zip(genRepr :: ??? :: HNil)
enc.value(zipped)
}
case class Foo(a: String, b: String, c: String)
derive[Foo, Nat._1 :: Nat._3 :: Nat.7 :: HNil]
The result will have to match an encoderTuple[H, N <: Nat, T <: HList]: Encoder[(H, N) :: T] or and encoderHList[H, N <: Nat, T <: HList]: Encoder[(H::N::HNil) :: T].
The goal is to create a "customizable" ZipWithIndex.
I guess standard shapeless.ops.hlist.Zip should be enough for that:
trait Encoder[Z] {
type Out
def apply(z: Z): Out
}
object Encoder {
type Aux[Z, Out0] = Encoder[Z] { type Out = Out0 }
// implicits
}
trait Deriver[A]
object Deriver {
// implicits
}
def derive[A, I <: HList, R <: HList, Z <: HList](a: A, i: I)(implicit
gen: Generic.Aux[A, R],
zipper: Zip.Aux[R :: I :: HNil, Z],
enc: Lazy[Encoder.Aux[Z, Deriver[A]]]): Deriver[A] = {
val genRepr: R = gen.to(a)
val zipped: Z = zipper(genRepr :: i :: HNil)
enc.value(zipped)
}
case class Foo(a: String, b: String, c: String)
// derive[Foo, Nat._1 :: Nat._3 :: Nat._7 :: HNil, ???, ???]
derive(Foo("aaa", "bbb", "ccc"), Nat._1 :: Nat._3 :: Nat._7 :: HNil)

shapeless convert case class to HList and skip all option fields

I have the next class:
case class Foo(a: Option[Int], b: Option[String], c: Option[Double])
as you can see, all fields is optional, i want convert this class into HList or Tuple, like
val f1 = Foo(Some(1) , None, Some(3D))
val f2 = Foo(None, "foo")
val result1 = f1.to[Int::Double::HNil] // => 1::3D
val result2 = f2.to[String::HNil] // "foo"
is it possible, without reflection?
It might be possible to do this with existing type classes in Shapeless (something like NatTRel and RemoveAll), but I'm not 100% sure of that, and this is a case where I'd just write my own type class:
import shapeless._
trait OptionalPieces[L <: HList, S <: HList] {
def apply(l: L): Option[S]
}
object OptionalPieces extends LowPriorityOptionalPieces {
implicit val hnilOptionalPieces: OptionalPieces[HNil, HNil] =
new OptionalPieces[HNil, HNil] {
def apply(l: HNil): Option[HNil] = Some(HNil)
}
implicit def hconsOptionalPiecesMatch[H, T <: HList, S <: HList](implicit
opt: OptionalPieces[T, S]
): OptionalPieces[Option[H] :: T, H :: S] =
new OptionalPieces[Option[H] :: T, H :: S] {
def apply(l: Option[H] :: T): Option[H :: S] = for {
h <- l.head
t <- opt(l.tail)
} yield h :: t
}
}
sealed class LowPriorityOptionalPieces {
implicit def hconsOptionalPiecesNoMatch[H, T <: HList, S <: HList](implicit
opt: OptionalPieces[T, S]
): OptionalPieces[Option[H] :: T, S] =
new OptionalPieces[Option[H] :: T, S] {
def apply(l: Option[H] :: T): Option[S] = opt(l.tail)
}
}
This witnesses that L contains at least all of the elements of S wrapped in Option, in order, and gives you a way to unwrap them at runtime (safely).
We can then define a syntax helper class like this:
implicit class OptionalPiecesSyntax[A, R <: HList](a: A)(implicit
gen: Generic.Aux[A, R]
) {
def to[S <: HList](implicit op: OptionalPieces[gen.Repr, S]): Option[S] =
op(gen.to(a))
}
And then:
scala> val f1 = Foo(Some(1) , None, Some(3D))
f1: Foo = Foo(Some(1),None,Some(3.0))
scala> val f2 = Foo(None, Some("foo"), None)
f2: Foo = Foo(None,Some(foo),None)
scala> val result1 = f1.to[Int :: Double :: HNil]
result1: Option[shapeless.::[Int,shapeless.::[Double,shapeless.HNil]]] = Some(1 :: 3.0 :: HNil)
scala> val result2 = f2.to[String :: HNil]
result2: Option[shapeless.::[String,shapeless.HNil]] = Some(foo :: HNil)
If you really wanted exceptions, you could just call .get in the syntax class, but that seems like a bad idea.