I am trying to generate a typeclass for returning the value of a case class field that has a particular annotation at compile time, using shapeless.
That's given a Scala annotation case class and a generic case class the typeclass Identity[T]
should return the value of the 'single' attribute annotated with such an annotation.
trait Identity[T] {
def apply(t: T): Long
}
final case class Id() extends scala.annotation.StaticAnnotation
case class User(#Id id: Long, account_id: Int, name: String, updated_at: java.time.Instant)
and given an instance of the case class and typeclass
val identity = Identity[User] // implicit summoner
val user = User(1009, 101, "Alessandro", Instant.now())
val value = identity(user)
I'd like value to return 1009
I tried to play around the following snippet, but I did get as far as to compute the Symbol name of the annotated field.
object WithShapeless {
import MyAnnotations.Id
import shapeless._
import shapeless.ops.hlist
import shapeless.ops.record.Keys
import shapeless.record._
// select the field symbol with the desired annotation, if exists
object Mapper extends Poly1 {
implicit def some[K <: Symbol]: Case.Aux[(K, Some[Id]), Option[K]] = at[(K, Some[Id])] {
case (k, _) => Some(k)
}
implicit def none[K <: Symbol]: Case.Aux[(K, None.type), Option[K]] = at[(K, None.type)] {
case (k, _) => Option.empty[K]
}
}
implicit def gen[A, HL <: HList, AL <: HList, KL <: HList, ZL <: HList, ML <: HList](
implicit
generic: LabelledGeneric.Aux[A, HL],
annots: Annotations.Aux[Id, A, AL],
keys: Keys.Aux[HL, KL],
zip: hlist.Zip.Aux[KL :: AL :: HNil, ZL],
mapper: hlist.Mapper.Aux[Mapper.type, ZL, ML],
ev0: hlist.ToList[ML, Option[Symbol]]
): Identity[A] = new Identity[A] {
val zipped: ZL = zip(keys() :: annots() :: HNil)
val annotatedFields: ML = mapper.apply(zipped)
val symbol: Symbol = annotatedFields.to[List].find(_.isDefined).get match {
case Some(symbol) => symbol
case _ => throw new Exception(s"Class has no attribute marked with #IdAnnot")
}
println(s"""
|zipped: ${zipped}
|mapped: ${annotatedFields}
|symbol: $symbol
|""".stripMargin)
override def apply(a: A): Long = {
val repr = generic.to(a)
val value = repr.get(Witness(symbol)) // compilation fails here
println(s"""
|Generic ${generic.to(a)}
|value: $value
""".stripMargin)
1
}
}
}
I try to conjure a Selector to return the value but the compiler fails with No field this.symbol.type in record HL.
I cannot get it to work!
Thanks
Actually you don't need LabelledGeneric because you don't use keys. Try
import java.time.Instant
import shapeless.ops.hlist.{CollectFirst, Zip}
import shapeless.{::, Annotations, Generic, HList, HNil, Poly1}
trait Identity[T] {
type Out
def apply(t: T): Out
}
object Identity {
type Aux[T, Out0] = Identity[T] { type Out = Out0 }
def instance[T, Out0](f: T => Out0): Aux[T, Out0] = new Identity[T] {
type Out = Out0
override def apply(t: T): Out = f(t)
}
def apply[T](implicit identity: Identity[T]): Aux[T, identity.Out] = identity
implicit def mkIdentity[T, HL <: HList, AL <: HList, ZL <: HList](implicit
generic: Generic.Aux[T, HL],
annotations: Annotations.Aux[Id, T, AL],
zip: Zip.Aux[HL :: AL :: HNil, ZL],
collectFirst: CollectFirst[ZL, Mapper.type],
): Aux[T, collectFirst.Out] =
instance(t => collectFirst(zip(generic.to(t) :: annotations() :: HNil)))
}
object Mapper extends Poly1 {
implicit def cse[A]: Case.Aux[(A, Some[Id]), A] = at(_._1)
}
final case class Id() extends scala.annotation.StaticAnnotation
case class User(#Id id: Long, account_id: Int, name: String, updated_at: java.time.Instant)
val identity = Identity[User]
val user = User(1009, 101, "Alessandro", Instant.now())
val value = identity(user) // 1009
Related
Here is a simplified version of my code.
How can I avoid to call asInstanceOf (because it is a smell for a poorly design solution) ?
sealed trait Location
final case class Single(bucket: String) extends Location
final case class Multi(buckets: Seq[String]) extends Location
#SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf"))
class Log[L <: Location](location: L, path: String) { // I prefer composition over inheritance
// I don't want to pass location to this method because it's a property of the object
// It's a separated function because there is another caller
private def getSinglePath()(implicit ev: L <:< Single): String = s"fs://${location.bucket}/$path"
def getPaths(): Seq[String] =
location match {
case _: Single => Seq(this.asInstanceOf[Log[_ <: Single]].getSinglePath())
case m: Multi => m.buckets.map(bucket => s"fs://${bucket}/$path")
}
}
Try a type class
class Log[L <: Location](location: L, val path: String) {
def getSinglePath()(implicit ev: L <:< Single): String = s"fs://${location.bucket}/$path"
def getPaths()(implicit gp: GetPaths[L]): Seq[String] = gp.getPaths(location, this)
}
trait GetPaths[L <: Location] {
def getPaths(location: L, log: Log[L]): Seq[String]
}
object GetPaths {
implicit val single: GetPaths[Single] = (_, log) => Seq(log.getSinglePath())
implicit val multi: GetPaths[Multi] = (m, log) => m.buckets.map(bucket => s"fs://${bucket}/${log.path}")
}
Normally a type class is a compile-time replacement for pattern matching.
I had to make getSinglePath public and path a val in order to provide access to them inside GetPaths. If you don't want to do so you can make the type class nested into Log
class Log[L <: Location](location: L, path: String) {
private def getSinglePath()(implicit ev: L <:< Single): String = s"fs://${location.bucket}/$path"
def getPaths()(implicit gp: GetPaths[L]): Seq[String] = gp.getPaths(location)
private trait GetPaths[L1 <: Location] {
def getPaths(location: L1): Seq[String]
}
private object GetPaths {
implicit def single(implicit ev: L <:< Single): GetPaths[L] = _ => Seq(getSinglePath())
implicit val multi: GetPaths[Multi] = _.buckets.map(bucket => s"fs://${bucket}/$path")
}
}
Actually we don't have to pass location explicitly and we don't need L1
class Log[L <: Location](location: L, path: String) {
private def getSinglePath()(implicit ev: L <:< Single): String = s"fs://${location.bucket}/$path"
def getPaths()(implicit gp: GetPaths): Seq[String] = gp.getPaths()
private trait GetPaths {
def getPaths(): Seq[String]
}
private object GetPaths {
implicit def single(implicit ev: L <:< Single): GetPaths = () => Seq(getSinglePath())
implicit def multi(implicit ev: L <:< Multi): GetPaths = () => location.buckets.map(bucket => s"fs://${bucket}/$path")
}
}
Now GetPaths is zero-parameter type class and slightly similar to magnet pattern.
I have the following code:
sealed trait Animal
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
trait Show[A] {
def show(a: A): String
}
class Processor[A](a: A) {
def print(implicit S: Show[A]): Unit = println(S.show(a))
}
implicit val showCat: Show[Cat] = c => s"Cat=${c.name}"
implicit val showDog: Show[Dog] = d => s"Dog=${d.name}"
val garfield = Cat("Garfield")
val odie = Dog("Odie")
val myPets = List(garfield, odie)
for (p <- myPets) {
val processor = new Processor(p)
processor.print // THIS FAILS AT THE MOMENT
}
Does anyone know of a nice way to get that line processor.print working?
I can think of 2 solutions:
pattern match the p in the for loop.
create an instance of Show[Animal] and pattern match it against all its subtypes.
But I'm wondering if there's a better way of doing this.
Thanks in advance!
Compile error is
could not find implicit value for parameter S: Show[Product with Animal with java.io.Serializable]
You can make Animal extend Product and Serializable
sealed trait Animal extends Product with Serializable
https://typelevel.org/blog/2018/05/09/product-with-serializable.html
Also instead of defining implicit Show[Animal] manually
implicit val showAnimal: Show[Animal] = {
case x: Cat => implicitly[Show[Cat]].show(x)
case x: Dog => implicitly[Show[Dog]].show(x)
// ...
}
you can derive Show for sealed traits (having instances for descendants) with macros
def derive[A]: Show[A] = macro impl[A]
def impl[A: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
val typA = weakTypeOf[A]
val subclasses = typA.typeSymbol.asClass.knownDirectSubclasses
val cases = subclasses.map{ subclass =>
cq"x: $subclass => _root_.scala.Predef.implicitly[Show[$subclass]].show(x)"
}
q"""
new Show[$typA] {
def show(a: $typA): _root_.java.lang.String = a match {
case ..$cases
}
}"""
}
implicit val showAnimal: Show[Animal] = derive[Animal]
or Shapeless
implicit val showCnil: Show[CNil] = _.impossible
implicit def showCcons[H, T <: Coproduct](implicit
hShow: Show[H],
tShow: Show[T]
): Show[H :+: T] = _.eliminate(hShow.show, tShow.show)
implicit def showGen[A, C <: Coproduct](implicit
gen: Generic.Aux[A, C],
show: Show[C]
): Show[A] = a => show.show(gen.to(a))
or Magnolia
object ShowDerivation {
type Typeclass[T] = Show[T]
def combine[T](ctx: CaseClass[Show, T]): Show[T] = null
def dispatch[T](ctx: SealedTrait[Show, T]): Show[T] =
value => ctx.dispatch(value) { sub =>
sub.typeclass.show(sub.cast(value))
}
implicit def gen[T]: Show[T] = macro Magnolia.gen[T]
}
import ShowDerivation.gen
or Scalaz-deriving
#scalaz.annotation.deriving(Show)
sealed trait Animal extends Product with Serializable
object Show {
implicit val showDeriving: Deriving[Show] = new Decidablez[Show] {
override def dividez[Z, A <: TList, ShowA <: TList](tcs: Prod[ShowA])(
g: Z => Prod[A]
)(implicit
ev: A PairedWith ShowA
): Show[Z] = null
override def choosez[Z, A <: TList, ShowA <: TList](tcs: Prod[ShowA])(
g: Z => Cop[A]
)(implicit
ev: A PairedWith ShowA
): Show[Z] = z => {
val x = g(z).zip(tcs)
x.b.value.show(x.a)
}
}
}
For cats.Show with Kittens you can write just
implicit val showAnimal: Show[Animal] = cats.derived.semi.show
The thing is that garfield and odie in List(garfield, odie) have the same type and it's Animal instead of Cat and Dog. If you don't want to define instance of type class for parent type you can use list-like structure preserving types of individual elements, HList garfield :: odie :: HNil.
For comparison deriving type classes in Scala 3
How to access parameter list of case class in a dotty macro
The most general solution is to just pack the typeclass instances in at the creation of myPets, existentially
final case class Packaged[+T, +P](wit: T, prf: P)
type WithInstance[T, +P[_ <: T]] = Packaged[U, P[U]] forSome { type U <: T }
implicit def packageInstance[T, U <: T, P[_ <: T]]
(wit: U)(implicit prf: P[U])
: T WithInstance P
= Packaged(wit, prf)
val myPets = List[Animal WithInstance Show](garfield, odie)
for(Packaged(p, showP) <- myPets) {
implicit val showP1 = showP
new Processor(p).print // note: should be def print()(implicit S: Show[A]), so that this can be .print()
}
Is it possible to somehow extend the solution to a sum type?
sealed trait Group
case class A extends Group
case class B extends Group
case class C extends Group
def divide(l : List[Group]): //Something from what I can extract List[A], List[B] and List[C]
May be you can try improving this answer. This may not solve your problem as it is difficult to know the the arbitrary subtypes of a given type (Group type might have any number of subtypes). In the case of Either it is easy to predict it's subtype as Right or Left.
sealed trait Group
case class A(name:String) extends Group
case class B(name:String) extends Group
case class C(name:String) extends Group
val list = List(
A("a1"), A("a2"), A("a3"), A("a4"),
B("b1"), B("b2"), B("b3"), B("b4"),
C("c1"), C("c2"), C("c3"), C("c4")
)
def divide(
list: List[Group],
aList : List[A],
bList: List[B],
cList: List[C]
): (List[A], List[B], List[C]) = {
list match {
case Nil => (aList, bList, cList)
case head :: tail => head match {
case a : A => divide(tail, aList.:+(a), bList, cList)
case b : B => divide(tail,aList, bList.:+(b), cList)
case c : C => divide(tail, aList, bList, cList.:+(c))
}
}
}
divide(list, List.empty[A], List.empty[B], List.empty[C])
//res1: (List[A], List[B], List[C]) = (List(A(a1), A(a2), A(a3), A(a4)),List(B(b1), B(b2), B(b3), B(b4)),List(C(c1), C(c2), C(c3), C(c4)))
Hope this helps you.
Have done it myself using Shapeless:
import shapeless.{:+:, ::, CNil, Coproduct, Generic, HList, HNil}
/*
Suppose we have a sealed trait and few implementations:
sealed trait Animal
case class Cat(a: Int) extends Animal
case class Dog(b: Int) extends Animal
case class Fox(c: Int) extends Animal
and a list:
val animals: List[Animal]
how to split the list into sub-lists per a subclass?
val cats: List[Cat] = ???
val dogs: List[Dog] = ???
val foxes: List[Fox] = ???
Of course it must work w/o boilerplate for arbitrary numbers of children
*/
object Split {
trait Splitter[T <: Coproduct] {
type R <: HList
def split(list: List[T]): R
}
type Aux[T <: Coproduct, R0 <: HList] = Splitter[T] {
type R = R0
}
implicit val cNilSplitter = new Splitter[CNil] {
type R = HNil
override def split(list: List[CNil]): HNil = HNil
}
implicit def cPllusSplitter[H, T <: Coproduct, R <: HList](implicit ev: Aux[T, R]): Aux[H :+: T, List[H] :: ev.R] = new Splitter[H :+: T] {
type R = List[H] :: ev.R
override def split(list: List[H :+: T]): ::[List[H], ev.R] = {
val heads: List[H] = list.flatMap(e => e.eliminate(h => Some(h), t => None))
val tails: List[T] = list.flatMap(e => e.eliminate(h => None, t => Some(t)))
val sub: ev.R = ev.split(tails)
heads :: sub
}
}
def splitCoproduct[T <: Coproduct, R <: HList](list: List[T])(implicit ev: Aux[T, R]): R = ev.split(list)
def split[X, T <: Coproduct, R <: HList](list: List[X])(implicit gen: Generic.Aux[X, T], ev: Aux[T, R]): R = {
val asCoproduct: List[T] = list.map(gen.to)
splitCoproduct[T, R](asCoproduct)(ev)
}
}
object Runner {
import Split._
def main(args: Array[String]): Unit = {
sealed trait Animal
case class Cat(a: Int) extends Animal
case class Dog(b: Int) extends Animal
case class Fox(c: Int) extends Animal
val animals: List[Animal] = List(Cat(1), Dog(1), Cat(2), Fox(1), Dog(2), Dog(3))
val result = split(animals) //List[Cat] :: List[Dog] :: List[Fox] :: HNil
println(result)
val cats: List[Cat] = result.head
val dogs: List[Dog] = result.tail.head
val foxes: List[Fox] = result.tail.tail.head
println(cats)
println(dogs)
println(foxes)
}
}
I'm trying to generate objects via shapeless
With this code snippet:
import shapeless._
case class T1(i: Int, v: String)
class WorkbenchSpec extends FlatSpec with Matchers {
"Gen tests" should "do smth interesting" in {
println(ObjectGenerators.gen(classOf[T1]))
}
}
object FieldGen extends Poly0 {
implicit val genInt = at[Int](0)
implicit val genString = at[String]("")
}
object ObjectGenerators {
def gen[T, TRepr <: HList](c: Class[T])
(implicit
gen: Generic.Aux[T, TRepr],
fill: ops.hlist.FillWith[FieldGen.type, TRepr]
): T = {
gen.from(HList.fillWith[TRepr](FieldGen))
}
def fillList[T <: HList](implicit fill: ops.hlist.FillWith[FieldGen.type, T]) =
fill()
}
I get an error:
could not find implicit value for parameter fillWith:
shapeless.ops.hlist.FillWith[com.media.gr.core.ObjectGenerators.FieldGen.type,gen.Repr]
gen.from(HList.fillWithgen.Repr)
But in sbt console if I do:
case class T1(i: Int, v: String)
object FieldGen extends Poly0 {
implicit val genInt = at[Int](0)
implicit val genString = at[String]("")
}
def gen[T, TRepr <: HList](c: Class[T])
(implicit
gen: Generic.Aux[T, TRepr],
fill: ops.hlist.FillWith[FieldGen.type, TRepr]
): T = {
gen.from(HList.fillWith[TRepr](FieldGen))
}
I get generated result. What am i doing wrong?
I'm working on a generic conversion library and I'd like to add automatic typeclass derivation using shapeless. It works in the general case but I'd like to introduce Haskellesque newtypes with automatic unwrapping and so I'd like to specialize the deriving function for my NewType type but scalac still picks up the more generic implicit value. Here's the code so far:
import shapeless._, shapeless.syntax._
trait NewType[A] { val value: A }
sealed trait ConversionTree
case class CInt(value: Int) extends ConversionTree
case class CBoolean(value: Boolean) extends ConversionTree
case class CString(value: String) extends ConversionTree
case class CArray(value: List[ConversionTree]) extends ConversionTree
case class CObject(values: Map[String, ConversionTree]) extends ConversionTree
def mergeCObjects(o1: CObject, o2: CObject): CObject = CObject(o1.values ++ o2.values)
trait ConvertsTo[A] {
def convertTo(value: A): ConversionTree
}
object ConvertsTo {
implicit val intConverter = new ConvertsTo[Int] { ... }
implicit val boolConverter = new ConvertsTo[Boolean] { ... }
implicit val stringConverter = new ConvertsTo[String] { ... }
implicit def arrayConverter[A](implicit convertInner: ConvertsTo[A]) = new ConvertsTo[List[A]] { ... }
implicit def objectConverter[A](implicit convertInner: ConvertsTo[A]) = new ConvertsTo[Map[String, A]] { ... }
implicit def newTypeConverter[A](implicit convertInner: ConvertsTo[A]): ConvertsTo[NewType[A]] = new ConvertsTo[NewType[A]] {
override def convertTo(value: NewType[A]): ConversionTree = {
convertInner.convertTo(value.value)
}
}
implicit def deriveHNil: ConvertsTo[HNil] = new ConvertsTo[HNil] {
override def convertTo(value: HNil): ConversionTree = CObject(Map[String, ConversionTree]())
}
// This is the generic case
implicit def deriveHCons[K <: Symbol, V, T <: HList](
implicit
key: Witness.Aux[K],
sv: Lazy[ConvertsTo[V]],
st: Lazy[ConvertsTo[T]]
): ConvertsTo[FieldType[K, V] :: T] = new ConvertsTo[FieldType[K, V] :: T]{
override def convertTo(value: FieldType[K, V] :: T): ConversionTree = {
val head = sv.value.convertTo(value.head)
val tail = st.value.convertTo(value.tail)
mergeCObjects(CObject(Map(key.value.name -> head)), tail.asInstanceOf[CObject])
}
}
// This is the special case for NewTypes
implicit def deriveHConsNewType[K <: Symbol, V, T <: HList](
implicit
key: Witness.Aux[K],
sv: Lazy[ConvertsTo[V]],
st: Lazy[ConvertsTo[T]]
): ConvertsTo[FieldType[K, NewType[V]] :: T] = new ConvertsTo[FieldType[K, NewType[V]] :: T] {
override def convertTo(value: FieldType[K, NewType[V]] :: T): ConversionTree = {
val head = sv.value.convertTo(value.head.value)
val tail = st.value.convertTo(value.tail)
mergeCObjects(CObject(Map(key.value.name -> head)), tail.asInstanceOf[CObject])
}
}
implicit def deriveInstance[F, G](implicit gen: LabelledGeneric.Aux[F, G], sg: Lazy[ConvertsTo[G]]): ConvertsTo[F] = {
new ConvertsTo[F] {
override def convertTo(value: F): ConversionTree = sg.value.convertTo(gen.to(value))
}
}
}
def convertToG[A](value: A)(implicit converter: ConvertsTo[A]): ConversionTree = converter.convertTo(value)
case class Id(value: String) extends NewType[String]
case class User(id: Id, name: String)
println(s"${ConvertsTo.newTypeConverter[String].convertTo(Id("id1"))}")
println(s"User: ${convertToG(User(Id("id1"), "user1"))}")
Output of the first println: CString("id1")
Output of the second println: CObject(Map("id" -> CObject(Map("value" -> CString("id1"))), "name" -> CString("user1")))
I'd like to get rid of the extra CObject around the id field in the second println. As you can see, calling the newTypeConverter directly results in the correct output but it doesn't work when the NewType is embedded in an object(and if I put a breakpoint in the deriveHConsNewType.convertTo method I can verify that it doesn't get called).
I've tried to define deriveHConsNewType like this as well but it didn't help:
implicit def deriveHConsNewType[K <: Symbol, V, N <: NewType[V], T <: HList](
implicit
key: Witness.Aux[K],
sv: Lazy[ConvertsTo[V]],
st: Lazy[ConvertsTo[T]]
): ConvertsTo[FieldType[K, N] :: T] = new ConvertsTo[FieldType[K, N] :: T] { ... }
Can someone explain me how the implicit search works when this kind of overlap occurs and provide a solution to my problem?
EDIT: Solved the issue by making the type variable of ConvertsTo contravariant, scalac now picks the specialized implicit value.
Solved the issue by making the type variable of ConvertsTo contravariant, scalac now picks the specialized implicit value.
trait ConvertsTo[-A] {
def convertTo(value: A): ConversionTree
}