I very roughly have the following code:
object MyObj {
def callWithParams(params: List[Param]): String = "some string"
}
sealed trait Param
case class Single(id: Int) extends Param
case class Group(id: Int, subParams: List[Param]) extends Param
def buildMyParams(): List[Param] = List(Single(1), Group(2, List(Group(3, Single(4))))
def macroImpl(c: blackbox.Context): c.Expr[String] = {
import c.universe._
// TODO: need a implicit Lift[Param] implementation here
val myParams = buildMyParams()
c.Expr[String](q"MyObj.callWithParams($myParams)")
}
My attempt at the implicit List[Param] is:
implicit val lift = Liftable[Param]({
case s: Single => q"Single(${s.id})"
case g: Group => q"Group(${g.id}, ${g.subParams})"
})
This doesn't compile because of the forward reference in the Liftable when trying to lift a Group, because of Group.subParams.
How do I get around this issue?
Declaring the lift as a def rather than a val was enough to fix this:
implicit def lift = Liftable[Param]({
case s: Single => q"Single(${s.id})"
case g: Group => q"Group(${g.id}, ${g.subParams})"
})
Related
In Scala 2.13 I have a case where I define some operation for all of types extending some sealed trait EnumType. I made it working but I'd like getTypeClass function not to be dependant on concrete types extending EnumType. Now I have to visit this function any time EnumType changes and add or remove some pattern. Is there a way to get instances of Operation type class for EnumType types but without pattern matching on all of them?
sealed trait EnumType
case object Add10 extends EnumType
case object Add50 extends EnumType
trait Operation[+T] {
def op(a: Int): Int
}
implicit val add10: Operation[Add10.type] = (a: Int) => a + 10
implicit val add50: Operation[Add50.type] = (a: Int) => a + 50
def getTypeClass(enumType: EnumType): Operation[EnumType] = {
// I need to modify this pattern matching
// every time EnumType changes
enumType match {
case Add10 => implicitly[Operation[Add10.type]]
case Add50 => implicitly[Operation[Add50.type]]
}
// I'd wish it could be done with without pattern matching like (it does not compile):
// implicitly[Operation[concrete_type_of(enumType)]]
}
// value of enumType is dynamic - it can be even decoded from some json document
val enumType: EnumType = Add50
println(getTypeClass(enumType).op(10)) // prints 60
EDIT
That's the way I wish it was called without using explicit subtypes of EnumType (using circe in this example to decode json) :
case class Doc(enumType: EnumType, param: Int)
implicit val docDecoder: Decoder[Doc] = deriveDecoder
implicit val enumTypeDecoder: Decoder[EnumType] = deriveEnumerationDecoder
decode[Doc]("""{"enumType": "Add10", "param": 10}""").map {
doc =>
println(getTypeClass(doc.enumType).call(doc.param))
}
Since you only know that statically enumType has type just EnumType and want to match based on runtime value/runtime class of enumType, you'll have to use some kind of reflection:
either runtime compilation with reflective toolbox (resolving implicits at runtime)
// libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value
import scala.reflect.runtime.{currentMirror => cm}
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox
val tb = cm.mkToolBox()
def getTypeClass(enumType: EnumType): Operation[EnumType] =
tb.eval(q"_root_.scala.Predef.implicitly[Operation[${cm.moduleSymbol(enumType.getClass)}]]")
.asInstanceOf[Operation[EnumType]]
or
def getTypeClass(enumType: EnumType): Operation[EnumType] =
tb.eval(tb.untypecheck(tb.inferImplicitValue(
appliedType(
typeOf[Operation[_]].typeConstructor,
cm.moduleSymbol(enumType.getClass).moduleClass.asClass.toType
),
silent = false
))).asInstanceOf[Operation[EnumType]]
or
def getTypeClass(enumType: EnumType): Operation[EnumType] = {
val cases = typeOf[EnumType].typeSymbol.asClass.knownDirectSubclasses.map(subclass => {
val module = subclass.asClass.module
val pattern = pq"`$module`"
cq"$pattern => _root_.scala.Predef.implicitly[Operation[$module.type]]"
})
tb.eval(q"(et: EnumType) => et match { case ..$cases }")
.asInstanceOf[EnumType => Operation[EnumType]]
.apply(enumType)
}
or a macro (automating the pattern matching)
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def getTypeClass(enumType: EnumType): Operation[EnumType] = macro getTypeClassImpl
def getTypeClassImpl(c: blackbox.Context)(enumType: c.Tree): c.Tree = {
import c.universe._
val cases = typeOf[EnumType].typeSymbol.asClass.knownDirectSubclasses.map(subclass => {
val module = subclass.asClass.module
val pattern = pq"`$module`"
cq"$pattern => _root_.scala.Predef.implicitly[Operation[$module.type]]"
})
q"$enumType match { case ..$cases }"
}
//scalac: App.this.enumType match {
// case Add10 => _root_.scala.Predef.implicitly[Macro.Operation[Add10.type]]
// case Add50 => _root_.scala.Predef.implicitly[Macro.Operation[Add50.type]]
//}
Since all the objects are defined at compile time I guess that a macro is better.
Covariant case class mapping to its base class without a type parameter and back
Getting subclasses of a sealed trait
Iteration over a sealed trait in Scala?
You can even make a macro whitebox, then using runtime reflection in the macro you can have Add50 type statically (if the class is known during macro expansion)
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
def getTypeClass(enumType: EnumType): Operation[EnumType] = macro getTypeClassImpl
def getTypeClassImpl(c: whitebox.Context)(enumType: c.Tree): c.Tree = {
import c.universe._
val clazz = c.eval(c.Expr[EnumType](c.untypecheck(enumType))).getClass
val rm = scala.reflect.runtime.currentMirror
val symbol = rm.moduleSymbol(clazz)
//q"_root_.scala.Predef.implicitly[Operation[${symbol.asInstanceOf[ModuleSymbol]}.type]]" // implicit not found
//q"_root_.scala.Predef.implicitly[Operation[${symbol/*.asInstanceOf[ModuleSymbol]*/.moduleClass.asClass.toType.asInstanceOf[Type]}]]" // implicit not found
// "migrating" symbol from runtime universe to macro universe
c.parse(s"_root_.scala.Predef.implicitly[Operation[${symbol.fullName}.type]]")
}
object App {
val enumType: EnumType = Add50
}
val operation = getTypeClass(App.enumType)
operation: Operation[Add50.type] // not just Operation[EnumType]
operation.op(10) // 60
How to accept only a specific subtype of existential type?
In a scala macro, how to get the full name that a class will have at runtime?
def getTypeClass[T <: EnumType : Operation](t: T) = implicitly[Operation[T]]
println(getTypeClass(Add50).op(10))
In fact, you don't even need getTypeClass at all:
def operate[T <: EnumType : Operation](t: T)(param: Int) = implicitly[Operation[T]].op(param)
println(operate(Add50)(10))
The Foo : Bar notation I used above is equivalent to this:
def operate[T <: EnumType](t: T)(param: Int)(op: Operation[T]) = op.op(param)
Note, that you are not actually using instances of Add50 and Add10 anywhere. They could just be traits rather than objects (IMO, that better reflects the intent):
sealed trait EnumType
trait Add10 extends EnumType
trait Add50 extends EnumType
trait Operation[+T] {
def op(a: Int): Int
}
implicit val add10: Operation[Add10] = (a: Int) => a + 10
implicit val add50: Operation[Add50] = (a: Int) => a + 50
def operate[T <: EnumType : Operation](param: Int) =
implicitly[Operation[T]].op(param)
println(operate[Add50](10))
check out the following example:
object MyTrait {
def apply(value: Int): MyTrait = value match {
case 0 => A
case 1 => B
case 2 => C
case _ => C
}
def unapply(arg: MyTrait): Int = arg.value
}
case object A extends MyTrait {
val value = 0
}
case object B extends MyTrait {
val value = 1
}
case object C extends MyTrait {
val value = 2
}
case class Test(value: MyTrait)
implicit val testFormatter = Json.format[Test]
all right, this code won't work because i didn't create a JSON serializer for my type "MyTrait". I could do it the way the Play Framework show us to do in the documentation, but it would make a json looking like
{ value: { whatever: 1 } }
and i would like it to look like
{ value: 1 }
In short, i would like my MyTrait objetcs to be interpreted as primitive types (Int) instead of a nested Json Oject.
If someone could help me with that i would be greatful.
Thank you by advance!
The documentation indicates how to provide custom Writes, there to MyTrait to serialize only it's inner value.
sealed trait MyTrait {
def value: Int
}
case object A extends MyTrait {
val value = 0
}
case object B extends MyTrait {
val value = 1
}
case object C extends MyTrait {
val value = 2
}
object MyTrait {
import play.api.libs.json._
def apply(value: Int): MyTrait = value match {
case 0 => A
case 1 => B
case 2 => C
case _ => C
}
def unapply(arg: MyTrait): Int = arg.value
// ---v
implicit val writes: Writes[MyTrait] =
Writes[MyTrait] { v => JsNumber(v.value) }
}
Then when serializing MyTrait instances (note the type ascription bellow is required as Writes is invariant):
Json.toJson(A: MyTrait)
// --> res2: play.api.libs.json.JsValue = 0
And so about Test class:
case class Test(value: MyTrait)
object Test {
import play.api.libs.json._
implicit val writes: OWrites[Test] = Json.writes[Test]
}
Json.toJson(Test(A))
// ---> res1: play.api.libs.json.JsValue = {"value":0}
I would rather recommend to have a look at Enumeratum for the enumerated type MyTrait, or to refactor MyTrait as a Value class and use Json.valueFormat as below.
import play.api.libs.json._
final class MyTrait private(val value: Int) extends AnyVal
object MyTrait {
val A = new MyTrait(0)
val B = new MyTrait(1)
val C = new MyTrait(2)
implicit val format: Format[MyTrait] = Json.valueFormat[MyTrait]
}
case class Test(value: MyTrait)
object Test {
implicit val format: OFormat[Test] = Json.format
}
scala> Json.toJson(Test(MyTrait.A))
res0: play.api.libs.json.JsValue = {"value":0}
Consider this code:
sealed trait Data
case class StringData(string: String) extends Data
case class IntData(int: Int) extends Data
trait Reader[A] {
def read(data: Data): A
}
implicit val stringReader: Reader[String] = {
case StringData(string) => string
case _ => sys.error("not a string")
}
implicit val intReader: Reader[Int] = {
case IntData(int) => int
case _ => sys.error("not an int")
}
With this in scope, I want to write an implicit method that silently converts from Data values to their "real" Scala values.
implicit def fromData[A: Reader](data: Data): A =
implicitly[Reader[A]].read(data)
But then, this code does not compile:
val str: String = StringData("foo")
val int: Int = IntData(420)
The error is a type mismatch. Standard debugging methods for implicits show that the A from fromData can't be infered (all implicit Readers are shown as applicable).
For your convenience, this is a link to a scastie of the code. In this other scastie, a similiar, yet different, and working snippet is presented.
My question: What is going on here?
Making the change to your Data class as well as your reader implicit conversion as below allows the code to compile.
import scala.language.implicitConversions
sealed trait Data[A]
case class StringData(string: String) extends Data[String]
case class IntData(int: Int) extends Data[Int]
trait Reader[A] {
def read(data: Data[A]): A
}
implicit val stringReader: Reader[String] = {
case StringData(string) => string
case _ => sys.error("not a string")
}
implicit val intReader: Reader[Int] = {
case IntData(int) => int
case _ => sys.error("not an int")
}
implicit def fromData[A](data: Data[A])(implicit ev: Reader[A]): A = ev.read(data)
val str: String = StringData("foo")
val int: Int = IntData(420)
You need data to be typed so that the compiler can infer which Reader should be used based on the type parameter of Data. Also notice that the following would not compile if you did not define an implicit Reader.
case class DoubleData(d: Double) extends Data[Double]
// Fails compilation because no implicit Reader exists.
val d: Double = DoubleData(5d)
I am trying to write a JsonFormat for an abstract class with a generic parameter that looks like something like this:
abstract class Animal[A] {
def data: A
def otherStuff: String = "stuff"
}
case class CatData(catField: String)
case class Cat(data: CatData) extends Animal[CatData]
So far my attempt at this looks like:
object AnimalProtocol extends DefaultJsonProtocol {
implicit val catDataFormat = jsonFormat1(CatData)
implicit val catFormat = jsonFormat1(Cat)
implicit def animalFormat[T <: Animal[T]](t: T)(implicit fmt: JsonWriter[T]) = new RootJsonFormat[Animal[T]] {
def write(obj: Animal[T]) = obj match {
case x: Cat => catFormat.write(x)
}
def read(json: JsValue) = ???
}
Now, if I try to do this:
import AnimalProtocol._
val cat: Animal[CatData] = Cat(CatData("this is cat data"))
I get the compiler error:
Cannot find JsonWriter or JsonFormat type class for Animal[CatData]
How can I make it work? In the end I want to write json with the fields in Animal and with data set to whatever case class applies.
You need to provide a type parameter for both the generic field and the subclass of Animal in your implicit def:
object AnimalProtocol2 extends DefaultJsonProtocol {
implicit val catDataFormat = jsonFormat1(CatData)
implicit def animalFormat[A, T <: Animal[A]](implicit fmt: JsonWriter[A]): RootJsonFormat[T] = new RootJsonFormat[T] {
def write(obj: T) = {
JsObject(
"data" -> obj.data.toJson,
"otherStuff" -> obj.otherStuff.toJson
)
}
def read(json: JsValue) = ???
}
}
That also allows you to get rid of pattern matching on subclasses inside animalFormat.
I don't use spray-json (I've got much more experience with play-json), but I'll try to help by pointing at a few strange things in your code.
I'm not sure you need implicit val catFormat = jsonFormat1(Cat), unless you want it to be applied instead of animalFormat when type is known to be Cat.
You definition of animalFormat looks wrong/strange for the following reasons:
type is strange, T <: Animal[T] doesn't correspond to your types i.e., you don't have CatData <: Animal[CatData]
you don't use t
you don't use fmt (but instead you pattern match on obj)
I would suggest to either define a static animalFormat, something like (not sure about the wildcard type _):
val animalFormat: RootJsonFormat[Animal[_]] = new RootJsonFormat[Animal[_]] {
def write(obj: Animal[_]) = {
JsObject(
"otherStuff" -> JsString(obj.otherStuff),
"data" -> obj match {
case x: Cat => catDataFormat.write(x.data)
}
)
def read(json: JsValue) = ???
}
or, without using pattern matching:
implicit def animalFormat[T](implicit fmt: JsonWriter[T]) = new RootJsonFormat[Animal[T]] {
def write(obj: Animal[T]) =
JsObject(
"otherStuff" -> JsString(obj.otherStuff),
"data" -> fmt.write(obj.data)
)
def read(json: JsValue) = ???
}
Note that with this approach, you won't be able to read a generic Animal as there's no type information in the json.
I wrote this slick code and it works as expected
object TestApp extends App {
implicit val getFooResult = GetResult(r => Foo(r.<<, r.<<))
val query = sql"select a, b from foo".as[Foo]
}
case class Foo(a: Long, b: String)
This compiles and runs fine. but If I change the code to
object TestApp extends App with MyImplicits {
val query = sql"select a, b from foo".as[Foo]
}
trait MyImplicits {
implicit val getFooResult = GetResult(r => Foo(r.<<, r.<<))
}
case class Foo(a: Long, b: String)
Now I get a compile error
could not find implicit value for parameter rconv: slick.jdbc.GetResult[Foo]
Same thing happens when I try
object TestApp extends App {
import MyImplicits.getFooResult
val query = sql"select a, b from foo".as[Foo]
}
object MyImplicits {
implicit val getFooResult = GetResult(r => Foo(r.<<, r.<<))
}
case class Foo(a: Long, b: String)
It still gives the same error and does not see that the implicit is defined in the object.
Why am I loosing the visibility to the implicit when I move it in a trait or object?