Consider the following:
sealed trait baseData {
def weight: Int
def priority: Int
}
case class data1(override val weight: Int, override val priority: Int) extends baseData
How would I define a function with the following signature that transforms data1 into an HList?
def toHlist[A <: baseData] (data: A) = {}
I want to pass in a trait instance into the toHlist function instead of the actual case class because there will be more than one case class extending the trait. I also don't want to hardcode any fields; I'm looking for a totally generic solution.
I'm sure this is doable with the Shapeless library, but haven't been able to figure out how.
EDIT
toHList needs to be able to handle a baseData pointer to a case class instance, as so:
val data: baseData = data1(1,2)
toHlist(data)
The reason for this is that we will have more than one case class extending baseData, and we will now know which one to pass to toHlist until run time.
This probably isn't the final answer, but it will help you refine your question,
scala> def toHlist[A <: baseData](data: A)(implicit gen: Generic[A]): gen.Repr = gen.to(data)
toHlist: [A <: baseData](data: A)(implicit gen: shapeless.Generic[A])gen.Repr
scala> toHlist(data1(23, 13))
res0: shapeless.::[Int,shapeless.::[Int,shapeless.HNil]] = 23 :: 13 :: HNil
Related
I have some code:
case class Name(name: String) extends StaticAnnotation
case class Example(#Name("baz") foo: String, bar: Int)
object nameAnnotationZip2 extends Poly2 {
implicit val newName: Case.Aux[Symbol, Some[Name], String] =
at((_, name) => name.value.name)
implicit val existingName: Case.Aux[Symbol, None.type, String] =
at((field, _) => field.name)
}
def renameRecord[
A,
Record <: HList,
Fields <: HList,
Annotations <: HList,
ZippedWith <: HList
](a: A)(implicit
generic: LabelledGeneric.Aux[A, Record],
fields: Keys.Aux[Record, Fields],
annotations: Annotations.Aux[Name, A, Annotations],
zippedWith: ZipWith.Aux[
Fields,
Annotations,
nameAnnotationZip2.type,
ZippedWith
]
) = {
val record = generic.to(a)
println(fields())
println(annotations())
zippedWith(fields(), annotations())
}
renameRecord(Example("qix", 5))
which attempts to create a record from an instance with renamed keys based on an annotation. unfortunately it always gives me a
could not find implicit value for parameter zippedWith: shapeless.ops.hlist.ZipWith.Aux[Fields,Annotations,nameAnnotationZip2.type,ZippedWith]
I haven't added the ZipWithKeys bit yet, but I believe I need to work around this issue first.
I believe I'm simply using Poly totally wrong somehow, but the compiler message isn't helping me find my error.
edit/part two:
I can work around by using Zip instead of ZipWith, converting to Sized, simply mapping the seq without Poly, and converting back to HList. what's the runtime performance difference between doing that and the above?
The type of Fields isn't Symbol, it's Symbol ## "foo". Case's generic parameters aren't variant so nameAnnotationZip2 isn't defined for the elements of Fields.
Adding a generic parameter to the cases fixes the issue
object nameAnnotationZip2 extends Poly2 {
implicit def newName[K <: Symbol]: Case.Aux[K, Some[Name], String] =
at((_, name) => name.value.name)
implicit def existingName[K <: Symbol]: Case.Aux[K, None.type, String] =
at((field, _) => field.name)
}
Is there a way to derive a type from an existing one in Scala?
For example, for case class Person(name: String, age: Int) I'd like to get a Product/Tuple of (Option[String], Option[Int]), i.e. a type mapped from an existing one.
There's a feature in Typescript (mapped types) that allows this relatively easily, which is how I started thinking down this path. But I'm not sure how something like this would be done in Scala.
I feel like the solution involves using shapeless in some way but I'm not sure how to get there.
I'd suggest parameterize the type as follows:
case class Person[F[_]](name: F[String], age: F[Int])
And then you can derive types you want, like
import cats.Id
type IdPerson = Person[Id]
type OptPerson = Person[Option]
Where cats.Id is simply defined as type Id[A] = A. It is straightforward to write your own, but I suggest using cats' one since it comes with useful typeclass instances.
With Shapeless you can define type class
import shapeless.ops.{hlist, product, tuple}
import shapeless.poly.~>
import shapeless.{Generic, HList, Id, the}
trait Partial[A] {
type Out
}
object Partial {
type Aux[A, Out0] = Partial[A] { type Out = Out0 }
object optionPoly extends (Id ~> Option) {
override def apply[T](t: T): Option[T] = null
}
// implicit def mkPartial[A, L <: HList, L1 <: HList](implicit
// generic: Generic.Aux[A, L],
// mapper: hlist.Mapper.Aux[optionPoly.type, L, L1],
// tupler: hlist.Tupler[L1]): Aux[A, tupler.Out] = null
implicit def mkPartial[A, T](implicit
toTuple: product.ToTuple.Aux[A, T],
mapper: tuple.Mapper[T, optionPoly.type],
): Aux[A, mapper.Out] = null
}
and use it (the is improved version of implicitly)
case class Person(name: String, age: Int)
// val pp = the[Partial[Person]]
// type PersonPartial = pp.Out
type PersonPartial = the.`Partial[Person]`.Out
implicitly[PersonPartial =:= (Option[String], Option[Int])]
If I have a method such as:
def f[T: Generic, U: Generic](t: T): U
Generic[T].to(t) returns type Generic[T]#Repr which I assume is a type alias for some type of HList.
Is it possible to select members from the HList and build another HList which I can convince the compiler is of type Generic[U]#Repr which I can then use to create an instance of U using Generic[U].from(myNewHList)?
I have tried many approaches but seem to be going around in circles.
When doing stuff like this in Shapeless, the best place to look is in the shapeless.ops typeclasses. In this case, since you know your second class is a strict subset of your first, Intersection is sufficient to get what you want. You'll want to set this up as a type class, so that you can pass in your input and output types and let the compiler infer the intermediate stuff.
trait Converter[A,B] {
def convert(a: A): B
}
object Converter {
def apply[A,B](implicit converter: Converter[A,B]) = converter
implicit def genericConverter[A, B, ARepr <: HList, BRepr <: HList](
implicit
genA: Generic.Aux[A,ARepr],
genB: Generic.Aux[B,BRepr],
intersection: shapeless.ops.hlist.Intersection.Aux[ARepr,BRepr,BRepr]
): Converter[A,B] =
new Converter[A,B]{def convert(a: A): B = genB.from(intersection(genA.to(a)))}
}
This can be used as follows:
case class Foo(a: Int, b: Int, c: String)
case class Bar(a: Int, c: String)
val foo = Foo(1,2,"Three")
val bar: Bar = Converter[Foo, Bar].convert(foo)
Using shapeless, one can use LabelledGeneric to update case class fields like so:
case class Test(id: Option[Long], name: String)
val test = Test(None, "Name")
val gen = LabelledGeneric[Test]
scala> gen.from(gen.to(test) + ('id ->> Option(1L)))
res0: Test = Test(Some(1),Name)
I would like the Test class (and others) to extend an abstract class Model, that will implement a method withId that would use a LabelledGeneric similar to the above code to update the id field, should it have one (which it should).
My attempt adds an implicit parameter of a LabelledGeneric[A] to the constructor of Model, which materializes just fine. I also need to somehow provide evidence to the record syntax that the LabelledGeneric#Repr has the id field to replace. Adding an implicit Updater parameter to withId satisfies the compiler, so that the code below will compile, but it is not usable.
import shapeless._, record._, ops.record._, labelled._, syntax.singleton._, tag._
abstract class Model[A](implicit gen: LabelledGeneric[A] { type Repr <: HList }) { this: A =>
def id: Option[Long]
val idWitness = Witness("id")
type F = FieldType[Symbol with Tagged[idWitness.T], Option[Long]]
def withId(id: Long)(implicit u: Updater.Aux[gen.Repr, F, gen.Repr]) =
gen.from(gen.to(this) + ('id ->> Option(id)))
}
case class Test(id: Option[Long], name: String) extends Model[Test]
When calling test.withId(1), the implicit Updater fails to materialize. The macro reports that gen.Repr isn't an HList type, when it in fact is. It seems that this match is the one that fails, where u baseType HConsSym returns <notype>. Equivalent to:
scala> weakTypeOf[test.gen.Repr].baseType(weakTypeOf[::[_, _]].typeConstructor.typeSymbol)
res12: reflect.runtime.universe.Type = <notype>
This is using shapeless 2.3, though it fails for different reasons in 2.2 (seems as though Updater had a large refactor).
Is it possible to accomplish this with shapeless, or am I way off target?
The main issue here is that the refined result type of the LabelledGeneric (Repr) is lost. At Model, the only thing known about Repr is Repr <: HList. The implicit Updater.Aux[gen.Repr, F, gen.Repr] searches for something that is only known as _ <: HList and thus fails to materialize.
You'd have to define Model with two type parameters
abstract class Model[A, L <: HList](implicit gen: LabelledGeneric.Aux[A, L])
but this doesn't allow you to write class Test extends Model[Test] and you have to write the labelled generic type by hand.
If you instead move the gen down to withId, you can make it work:
object Model {
private type IdField = Symbol with Tagged[Witness.`"id"`.T]
private val IdField = field[IdField]
type F = FieldType[IdField, Option[Long]]
}
abstract class Model[A] { this: A =>
import Model._
def id: Option[Long]
def withId[L <: HList](id: Long)(implicit // L captures the fully refined `Repr`
gen: LabelledGeneric.Aux[A, L], // <- in here ^
upd: Updater.Aux[L, F, L] // And can be used for the Updater
): A = {
val idf = IdField(Option(id))
gen.from(upd(gen.to(this), idf))
}
}
case class Test(id: Option[Long], name: String) extends Model[Test]
If you're concerned with resolution performance, you can cache the value(s) in the companion of Test:
case class Test(id: Option[Long], name: String) extends Model[Test]
object Test {
implicit val gen = LabelledGeneric[Test]
}
This would mean that code like this
val test = Test(None, "Name")
println("test.withId(12) = " + test.withId(12))
println("test.withId(12).withId(42) = " + test.withId(12).withId(42))
would use the definition of Test.gen instead of materializing a new LabelledGeneric every time.
This works for both, shapeless 2.2.x and 2.3.x.
There is a type that can be parametrized by a certain restricted set of types:
trait Base[T] { def f(t: T): List[T] }
implicit object StringBase extends Base[String] {
override def f(t: String) = t.toList.map(c => String.valueOf(c))
}
implicit object IntBase extends Base[Int] {
override def f(t: Int) = List(t,t,t)
}
Now, I can define a function that takes a collection of a specific type and processes it:
def consume[T : Base](xs: Seq[T]) =
xs.map(x => implicitly[Base[T]].f(x).mkString("-"))
How do I define a function that takes a sequence of objects for types of which there exists an implicit conversion to Base and do the same? In a type-safe way, of course.
If I was not entirely clear, here's what I'd like to have:
consume(Seq(1,"asd", 3)) // => Seq("1-1-1", "a-s-d", "3-3-3")
I'm sure I can achieve it with shapeless' HList, but what about core Scala? Anyway, putting shapeless tag in case functionally inclined guys are willing to help.
import shapeless._
import ops.hlist.Mapper
import ops.hlist.ToList
trait Base[T] { def f(t: T): List[T] }
implicit object StringBase extends Base[String] {
override def f(t: String) = t.toList.map(c => String.valueOf(c))
}
implicit object IntBase extends Base[Int] {
override def f(t: Int) = List(t,t,t)
}
object base extends Poly1 {
implicit def forBase[A : Base] = at[A](x => implicitly[Base[A]].f(x))
}
def consume[T <: HList, Inter <: HList](xs: T)
(implicit
mapBase: Mapper.Aux[base.type, T, Inter],
interToList: ToList[Inter, List[Any]]): Seq[String] = {
xs.map(base).toList.map(_.mkString("-"))
}
Two key parts:
object base extends Poly1 : This is a polymorphic function only defined on types with a Base typeclass instance. It calls Base.f on its arg.
The implicits and type params in consume :
T is our input HList type. Inter is an intermediate type variable that is the output type of the result of mapping base over T.
mapBase: Mapper.Aux[base.type, T, Inter] : This is evidence that if we map base over T we get Inter. We need this to call .map on the incoming hlist. By putting it in our implicits list, we just require that it exists when the function is called. Shapeless will generate this for us if it can. If it can't, it most likely means that you forgot to define a Base instance for a type in T
interToList: ToList[Inter, List[Any]] : This is evidence that you can convert the HList Inter to a List[List[Any]]. So List[Any] must be the least-upperbound type of all the types in Inter. Once again, by putting it in the input implicits, we just require that shapeless can generate this for us. This is just boilerplate to prove to the compiler that we can call toList and get a List[List[Any]]. I haven't found a way to avoid this and the Inter variable in my experiences sadly.
Now thanks to all those implicits, we just call xs.map(base) to get an HList out with the appropriate Base.f applied. Then we call .toList to get a List[List[Any]]. Then we map over those List[Any]s and mkString with dashes as you wanted. And out comes our Seq[String]!
Here is it working in the REPL:
scala> consume(1 :: "asd" :: 3 :: HNil)
res0: Seq[String] = List(1-1-1, a-s-d, 3-3-3)
Here is dirty hack for consume method. Not super type safe and nice but may be fine as a workaround.
trait Base[T] { def f(t: T): List[T] }
implicit object StringBase extends Base[String] {
override def f(t: String) = t.toList.map(c => String.valueOf(c))
}
implicit object IntBase extends Base[Int] {
override def f(t: Int) = List(t,t,t)
}
case class Consumable[T](v: T, base: Base[T])
implicit def toConsumable[T](v: T)(implicit base: Base[T], ev: ClassTag[T]) =
Consumable(v, base)
def consume(xs: Consumable[_]*) =
xs.map(x => x.base.asInstanceOf[Base[Any]].f(x.v).mkString("-"))
println(consume(1, 2, 3))
println(consume("a", "b", "c"))
println(consume(1, 2, "a", "b", "c"))