Zip generic HList with static Nat HList - scala

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)

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 filter a list of options

I am new to shapeless and try to tackle the following problem. I have tuples of different length with Option[(R[A], A)] as elements and would like to kind of filter the tuple so that it results only in Some. Notice that I don't have Some or None at compile time but only Option.
I think I have kind of a recursive problem but cannot describe it. I have something like the following in mind (where I have already converted the tuple into an HList:
def performFinalStep[A1](t: Tuple1[A1]) = ???
def performFinalStep[A1, A2](t: Tuple2[A1, A2]) = ???
...
def reduce[T <: HList, A <: HList](l: Option[(R[A], A)] :: T, acc: A) = {
case h :: HNil => performFinalStep(toTuple(h :: acc))
case h :: t => reduce(t, h :: acc) //does not work as it is not known if T's head is Option[(R[A], A)]
}
reduce((Option(R(1), 2), Option(R("S", "s"))).productElements, HNil)
There are two things I don't know, how do I turn an HList back into a tuple and how can I overcome the typing for T?
In Shapeless converting case classes to HLists and vice versa can be done via shapeless.Generic. And in Scala Tuples are case classes. But there is standard way
(1 :: "a" :: true :: HNil).tupled // (1,a,true)
import shapeless.ops.hlist.Tupler
def toTuple[L <: HList](l : L)(implicit tupler: Tupler[L]): tupler.Out = l.tupled
When you can't express something with a method you create a type class (I don't know your actual type instead of Nothing)
case class R[A](a: A)
trait Reduce[L <: HList, Acc <: HList] {
def apply(l: L, acc: Acc): Nothing
}
object Reduce {
implicit def singletonCase[A, Acc <: HList](implicit
tupler: Tupler[Option[(R[A], A)] :: Acc]): Reduce[Option[(R[A], A)] :: HNil, Acc] =
(l, acc) => l match {
case h :: HNil => performFinalStep(toTuple(h :: acc))
}
implicit def notSingletonCase[H, T <: HList, Acc <: HList](implicit
reduce: Reduce[T, H :: Acc]): Reduce[H :: T, Acc] =
(l, acc) => l match {
case h :: t => reduce(t, h :: acc)
}
}
def reduce[L <: HList, Acc <: HList](l: L, acc: Acc)(implicit
r: Reduce[L, Acc]): Nothing = r(l, acc)
Next trouble is
Error:(39, 27) overloaded method value performFinalStep with alternatives:
[A1, A2](t: (A1, A2))Nothing <and>
[A1](t: (A1,))Nothing
cannot be applied to (tupler.Out)
case h :: HNil => performFinalStep(toTuple(h :: acc))
You can try one more type class
trait PerformFinalStep[P <: Product] {
def apply(t: P): Nothing
}
object PerformFinalStep {
implicit def tuple1[A1]: PerformFinalStep[Tuple1[A1]] = t => ???
implicit def tuple2[A1, A2]: PerformFinalStep[Tuple2[A1, A2]] = t => ???
// ...
}
def performFinalStep[T <: Product](t: T)(implicit
pfs: PerformFinalStep[T]) = pfs(t)
trait Reduce[L <: HList, Acc <: HList] {
def apply(l: L, acc: Acc): Nothing
}
object Reduce {
implicit def singletonCase[A, Acc <: HList, P <: Product](implicit
tupler: Tupler.Aux[Option[(R[A], A)] :: Acc, P],
pfs: PerformFinalStep[P]): Reduce[Option[(R[A], A)] :: HNil, Acc] =
(l, acc) => l match {
case h :: HNil => performFinalStep(toTuple(h :: acc))
}
implicit def notSingletonCase[H, T <: HList, Acc <: HList](implicit
reduce: Reduce[T, H :: Acc]): Reduce[H :: T, Acc] =
(l, acc) => l match {
case h :: t => reduce(t, h :: acc)
}
}
def reduce[L <: HList, Acc <: HList](l: L, acc: Acc)(implicit
r: Reduce[L, Acc]): Nothing = r(l, acc)
Now reduce((Option(R(1), 2), Option(R("S"), "s")).productElements, HNil) produces "could not find implicit value for parameter" but implicitly[Reduce[Option[(R[Int], Int)] :: Option[(R[String], String)] :: HNil, HNil]] compiles. If we substitute explicitly
reduce((Option(R(1), 2), Option(R("S"), "s")).productElements, HNil)(
implicitly[Reduce[Option[(R[Int], Int)] :: Option[(R[String], String)] :: HNil, HNil]]
)
we'll have
Error:(52, 82) type mismatch;
found : Reduce[Option[(R[Int], Int)] :: Option[(R[String], String)] :: shapeless.HNil,shapeless.HNil]
required: Reduce[Option[(R[Int], Int)] :: Option[(R[String], String)] :: shapeless.HNil,shapeless.HNil.type]
Note: shapeless.HNil >: shapeless.HNil.type, but trait Reduce is invariant in type Acc.
You may wish to define Acc as -Acc instead. (SLS 4.5)
reduce((Option(R(1), 2), Option(R("S"), "s")).productElements, HNil)(implicitly[Reduce[Option[(R[Int], Int)] :: Option[(R[String], String)] :: HNil, HNil]])
So you should call it like
reduce((Option(R(1), 2), Option(R("S"), "s")).productElements, HNil : HNil)

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)

Converting case class to another recursively structural identical case class

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)

shapeless HList to TupleN where the tuple shape need not exactly match the HList shape

I would like to create the equivalent of:
def toTupleN[A1, ..., AN, L <: HList](l: L): TupleN[A1, ..., AN]
Code using toTupleN should compile only when there is exactly one N combination of values in l that the tuple could be created from. Anything else should generate a compile-time error. Available implicit conversions should be taken into account. Note that there are no restrictions on the size of l or the ordering of values in it.
Example:
val l = 23 :: (1, "wibble") :: (2, "wobble") :: "foo" :: HNil
// l: shapeless.::[Int,shapeless.::[(Int, String),shapeless.::[(Int, String),shapeless.::[String,shapeless.HNil]]]] = 23 :: (1,wibble) :: (2,wobble) :: foo :: HNil
val t2: (String, Int) = toTuple2(l)
// t2: (String, Int) = (foo,23)
val nope: (String, String) = toTuple2(l)
// Compiler error because no combination of l's values can create nope
val nein: ((Int, String)) = toTuple2(l)
// Another compiler error because there is more than one way l's values can create nein
This question arose from the answer to the following question. The more general machinery in this question could be used to both create data structures and call any standard function (whose arguments are of different types) using FunctionN#tupled.
Update:
Some examples to define the desired behavior with subtypes:
trait A
trait B extends A
trait C extends A
val a: A
val b: B
val c: C
toTuple2[(A, Int)](5 :: b :: HNil) // (b, 5): subtypes match supertypes when there is no exact match
toTuple2[(A, Int)](5 :: b :: a :: HNil) // (a, 5): only one exact match is available
toTuple2[(A, Int)](5 :: a :: a :: HNil) // compile error: more than one exact match is available
toTuple2[(A, Int)](5 :: b :: c :: HNil) // compile error: more than one inexact match is available
I haven't been able to make target type inference work out quite the way you wanted, but as compensation I've generalized to an arbitrary product type via shapeless's Generic,
import shapeless._, ops.hlist._, test._
object Demo {
trait UniqueSelect[L <: HList, M <: HList] {
def apply(l: L): M
}
object UniqueSelect {
implicit def hnil[L <: HList]: UniqueSelect[L, HNil] =
new UniqueSelect[L, HNil] {
def apply(l: L): HNil = HNil
}
implicit def hcons[L <: HList, H, T <: HList, S <: HList]
(implicit
pt: Partition.Aux[L, H, H :: HNil, S],
ust: UniqueSelect[S, T]
): UniqueSelect[L, H :: T] =
new UniqueSelect[L, H :: T] {
def apply(l: L): H :: T = {
val (h :: HNil, s) = pt(l)
h :: ust(s)
}
}
}
def toProductUniquely[P <: Product] = new ToProductUniquely[P]
class ToProductUniquely[P <: Product] {
def apply[L <: HList, M <: HList](l: L)
(implicit gen: Generic.Aux[P, M], up: UniqueSelect[L, M]): P =
gen.from(up(l))
}
val l = 23 :: (1, "wibble") :: (2, "wobble") :: "foo" :: HNil
val t2 = toProductUniquely[(String, Int)](l)
typed[(String, Int)](t2)
assert(t2 == ("foo", 23))
illTyped("""
toProductUniquely[(String, String)](l)
""")
illTyped("""
toProductUniquely[Tuple1[(Int, String)]](l)
""")
}
Update 1
Adding support for the selection being satisfied by subtypes of the requested types is fairly straightforward if we say that where we have types A and B <: A then the selection of A from A :: B :: HNil is ambiguous because both elements conform to A. This can be done by adding a SubtypeUnifier to the witnesses in the previous definition of hcons,
import shapeless._, ops.hlist._, test._
object Demo extends App {
trait UniqueSelect[L <: HList, M <: HList] {
def apply(l: L): M
}
object UniqueSelect {
implicit def hnil[L <: HList]: UniqueSelect[L, HNil] =
new UniqueSelect[L, HNil] {
def apply(l: L): HNil = HNil
}
implicit def hcons[L <: HList, M <: HList, H, T <: HList, S <: HList]
(implicit
su: SubtypeUnifier.Aux[L, H, M],
pt: Partition.Aux[M, H, H :: HNil, S],
upt: UniqueSelect[S, T]
): UniqueSelect[L, H :: T] =
new UniqueSelect[L, H :: T] {
def apply(l: L): H :: T = {
val (h :: HNil, s) = pt(su(l))
h :: upt(s)
}
}
}
def toProductUniquely[P <: Product] = new ToProductUniquely[P]
class ToProductUniquely[P <: Product] {
def apply[L <: HList, M <: HList](l: L)
(implicit gen: Generic.Aux[P, M], up: UniqueSelect[L, M]): P =
gen.from(up(l))
}
class A
class B extends A
class C
val ac = new A :: new C :: HNil
val bc = new B :: new C :: HNil
val abc = new A :: new B :: new C :: HNil
// Exact match
val tac = toProductUniquely[(A, C)](ac)
typed[(A, C)](tac)
// Subtype
val tbc = toProductUniquely[(A, C)](bc)
typed[(A, C)](tbc)
// Exact match again
val tabc = toProductUniquely[(B, C)](abc)
typed[(B, C)](tabc)
// Ambiguous due to both elements conforming to A
illTyped("""
toProductUniquely[(A, C)](abc)
""")
}
Update 2
We can also accommodate a unification semantics which gives preference to exact match and then falls back to a unique subtype as described in your updated question. We do this by combining the instances from the two solutions above: the exact match instance from the first at normal priority and the subtype match instance at low priority,
import shapeless._, ops.hlist._, test._
object Demo extends App {
trait UniqueSelect[L <: HList, M <: HList] {
def apply(l: L): M
}
object UniqueSelect extends UniqueSelect0 {
implicit def hnil[L <: HList]: UniqueSelect[L, HNil] =
new UniqueSelect[L, HNil] {
def apply(l: L): HNil = HNil
}
implicit def hconsExact[L <: HList, H, T <: HList, S <: HList]
(implicit
pt: Partition.Aux[L, H, H :: HNil, S],
upt: UniqueSelect[S, T]
): UniqueSelect[L, H :: T] =
new UniqueSelect[L, H :: T] {
def apply(l: L): H :: T = {
val (h :: HNil, s) = pt(l)
h :: upt(s)
}
}
}
trait UniqueSelect0 {
implicit def hconsSubtype[L <: HList, M <: HList, H, T <: HList, S <: HList]
(implicit
su: SubtypeUnifier.Aux[L, H, M],
pt: Partition.Aux[M, H, H :: HNil, S],
upt: UniqueSelect[S, T]
): UniqueSelect[L, H :: T] =
new UniqueSelect[L, H :: T] {
def apply(l: L): H :: T = {
val (h :: HNil, s) = pt(su(l))
h :: upt(s)
}
}
}
def toProductUniquely[P <: Product] = new ToProductUniquely[P]
class ToProductUniquely[P <: Product] {
def apply[L <: HList, M <: HList](l: L)
(implicit gen: Generic.Aux[P, M], up: UniqueSelect[L, M]): P = gen.from(up(l))
}
trait A
trait B extends A
trait C extends A
val a: A = new A {}
val b: B = new B {}
val c: C = new C {}
// (b, 5): subtypes match supertypes when there is no exact match
toProductUniquely[(A, Int)](5 :: b :: HNil)
// (a, 5): only one exact match is available
toProductUniquely[(A, Int)](5 :: b :: a :: HNil)
// compile error: more than one exact match is available
illTyped("""
toProductUniquely[(A, Int)](5 :: a :: a :: HNil)
""")
// compile error: more than one inexact match is available
illTyped("""
toProductUniquely[(A, Int)](5 :: b :: c :: HNil)
""")
}