Automatic case class mapping - scala

I'm building a web-application using Play and Slick, and find myself in a situation where the user-facing forms are similar, but not exactly the same as the database model.
Hence I have two very similar case classes, and need to map from one to another (e.g. while filling the form for rendering an "update" view).
In the case I'm interested in, the database model case class is a super-set of the form case-class, i.e. the only difference between both is that the database model has two more fields (two identifiers, basically).
What I'm now wondering about is whether there'd be a way to build a small library (e.g. macro-driven) to automatically populate the form case class from the database case class based on the member names. I've seen that it may be possible to access this kind of information via reflection using Paranamer, but I'd rather not venture into this.

Here is a solution using Dynamic because I wanted to try it out. A macro would decide statically whether to emit an apply of a source value method, the default value method, or just to supply a literal. The syntax could look something like newFrom[C](k). (Update: see below for the macro.)
import scala.language.dynamics
trait Invocable extends Dynamic {
import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.universe._
def applyDynamic(method: String)(source: Any) = {
require(method endsWith "From")
def caseMethod(s: Symbol) = s.asTerm.isCaseAccessor && s.asTerm.isMethod
val sm = currentMirror reflect source
val ms = sm.symbol.asClass.typeSignature.members filter caseMethod map (_.asMethod)
val values = ms map (m => (m.name, (sm reflectMethod m)()))
val im = currentMirror reflect this
invokeWith(im, method dropRight 4, values.toMap)
}
def invokeWith(im: InstanceMirror, name: String, values: Map[Name, Any]): Any = {
val at = TermName(name)
val ts = im.symbol.typeSignature
val method = (ts member at).asMethod
// supplied value or defarg or default val for type of p
def valueFor(p: Symbol, i: Int): Any = {
if (values contains p.name) values(p.name)
else ts member TermName(s"$name$$default$$${i+1}") match {
case NoSymbol =>
if (p.typeSignature.typeSymbol.asClass.isPrimitive) {
if (p.typeSignature <:< typeOf[Int]) 0
else if (p.typeSignature <:< typeOf[Double]) 0.0
else ???
} else null
case defarg => (im reflectMethod defarg.asMethod)()
}
}
val args = (for (ps <- method.paramss; p <- ps) yield p).zipWithIndex map (p => valueFor(p._1,p._2))
(im reflectMethod method)(args: _*)
}
}
case class C(a: String, b: Int, c: Double = 2.0, d: Double)
case class K(b: Int, e: String, a: String)
object C extends Invocable
object Test extends App {
val res = C applyFrom K(8, "oh", "kay")
Console println res // C(kay,8,2.0,0.0)
}
Update: Here is the macro version, more for fun than for profit:
import scala.language.experimental.macros
import scala.reflect.macros._
import scala.collection.mutable.ListBuffer
def newFrom[A, B](source: A): B = macro newFrom_[A, B]
def newFrom_[A: c.WeakTypeTag, B: c.WeakTypeTag](c: Context)(source: c.Expr[A]): c.Expr[B] = {
import c.{ literal, literalNull }
import c.universe._
import treeBuild._
import nme.{ CONSTRUCTOR => Ctor }
def caseMethod(s: Symbol) = s.asTerm.isCaseAccessor && s.asTerm.isMethod
def defaulter(name: Name, i: Int): String = s"${name.encoded}$$default$$${i+1}"
val noargs = List[c.Tree]()
// side effects: first evaluate the arg
val side = ListBuffer[c.Tree]()
val src = TermName(c freshName "src$")
side += ValDef(Modifiers(), src, TypeTree(source.tree.tpe), source.tree)
// take the arg as instance of a case class and use the case members
val a = implicitly[c.WeakTypeTag[A]].tpe
val srcs = (a.members filter caseMethod map (m => (m.name, m.asMethod))).toMap
// construct the target, using src fields, defaults (from the companion), or zero
val b = implicitly[c.WeakTypeTag[B]].tpe
val bm = b.typeSymbol.asClass.companionSymbol.asModule
val bc = bm.moduleClass.asClass.typeSignature
val ps = (b declaration Ctor).asMethod.paramss.flatten.zipWithIndex
val args: List[c.Tree] = ps map { case (p, i) =>
if (srcs contains p.name)
Select(Ident(src), p.name)
else bc member TermName(defaulter(Ctor, i)) match {
case NoSymbol =>
if (p.typeSignature.typeSymbol.asClass.isPrimitive) {
if (p.typeSignature <:< typeOf[Int]) literal(0).tree
else if (p.typeSignature <:< typeOf[Double]) literal(0.0).tree
else ???
} else literalNull.tree
case defarg => Select(mkAttributedRef(bm), defarg.name)
}
}
c.Expr(Block(side.toList, Apply(Select(New(mkAttributedIdent(b.typeSymbol)), Ctor), args)))
}
With usage:
case class C(a: String, b: Int, c: Double = 2.0, d: Double)
case class K(b: Int, e: String, a: String) { def i() = b }
val res = newFrom[K, C](K(8, "oh", "kay"))

Related

Scala 3 quotes: get default case class field value [duplicate]

Is there a clean way to access the default values of a case class fields when performing type class derivation in Scala 3 using Mirrors? For example:
case class Foo(s: String = "bar", i: Int, d: Double = Math.PI)
Mirror.Product.MirroredElemLabels will be set to ("s", "i", "d"). Is there anything like: (Some["bar"], None, Some[3.141592653589793])?
If not could this be achieved using Macros? Can I use the Mirrors and Macros simultaneously to derive a type class instance?
You'll have to write a macro working with methods named like <init>$default$1, <init>$default$2, ... in companion object
import scala.quoted.*
inline def printDefaults[T]: Unit = ${printDefaultsImpl[T]}
def printDefaultsImpl[T](using Quotes, Type[T]): Expr[Unit] = {
import quotes.reflect.*
(1 to 3).map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
).foreach(println)
'{()}
}
printDefaults[Foo]
//Some(Literal(Constant(bar)))
//None
//Some(Select(Ident(Math),PI))
Mirrors and macros can work together:
import scala.quoted.*
import scala.deriving.*
trait Default[T] {
type Out <: Tuple
def defaults: Out
}
object Default {
transparent inline given mkDefault[T](using
m: Mirror.ProductOf[T],
s: ValueOf[Tuple.Size[m.MirroredElemTypes]]
): Default[T] =
new Default[T] {
type Out = Tuple.Map[m.MirroredElemTypes, Option]
def defaults = getDefaults[T](s.value).asInstanceOf[Out]
}
inline def getDefaults[T](inline s: Int): Tuple = ${getDefaultsImpl[T]('s)}
def getDefaultsImpl[T](s: Expr[Int])(using Quotes, Type[T]): Expr[Tuple] = {
import quotes.reflect.*
val n = s.asTerm.underlying.asInstanceOf[Literal].constant.value.asInstanceOf[Int]
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
)
def exprOfOption[T](oet: Option[Expr[T]])(using Type[T], Quotes): Expr[Option[T]] = oet match {
case None => Expr(None)
case Some(et) => '{Some($et)}
}
val exprs: List[Option[Expr[Any]]] = terms.map(_.map(_.asExprOf[Any]))
val exprs1: List[Expr[Option[Any]]] = exprs.map(exprOfOption)
Expr.ofTupleFromSeq(exprs1)
}
}
Usage:
val d = summon[Default[Foo]]
summon[d.Out =:= (Option[String], Option[Int], Option[Double])] // compiles
d.defaults // (Some(bar),None,Some(3.141592653589793))
As Dmytro suggests, information is carried in methods <init>default$x of the class companion object.
However, Quotes discourages accessing a symbol's tree in a macro:
https://github.com/lampepfl/dotty/blob/main/library/src/scala/quoted/Quotes.scala#L3628.
Symbol's tree is lost, unless program is compiled with -Yretain-trees)
It is better to let the macro evaluate <init>default$x, rather than copy the right hand side of its definition.
One can do so by expressing terms as :
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.map(Select(Ref(TypeRepr.of[T].typeSymbol.companionModule),_))
)

Type Class Derivation accessing default values

Is there a clean way to access the default values of a case class fields when performing type class derivation in Scala 3 using Mirrors? For example:
case class Foo(s: String = "bar", i: Int, d: Double = Math.PI)
Mirror.Product.MirroredElemLabels will be set to ("s", "i", "d"). Is there anything like: (Some["bar"], None, Some[3.141592653589793])?
If not could this be achieved using Macros? Can I use the Mirrors and Macros simultaneously to derive a type class instance?
You'll have to write a macro working with methods named like <init>$default$1, <init>$default$2, ... in companion object
import scala.quoted.*
inline def printDefaults[T]: Unit = ${printDefaultsImpl[T]}
def printDefaultsImpl[T](using Quotes, Type[T]): Expr[Unit] = {
import quotes.reflect.*
(1 to 3).map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
).foreach(println)
'{()}
}
printDefaults[Foo]
//Some(Literal(Constant(bar)))
//None
//Some(Select(Ident(Math),PI))
Mirrors and macros can work together:
import scala.quoted.*
import scala.deriving.*
trait Default[T] {
type Out <: Tuple
def defaults: Out
}
object Default {
transparent inline given mkDefault[T](using
m: Mirror.ProductOf[T],
s: ValueOf[Tuple.Size[m.MirroredElemTypes]]
): Default[T] =
new Default[T] {
type Out = Tuple.Map[m.MirroredElemTypes, Option]
def defaults = getDefaults[T](s.value).asInstanceOf[Out]
}
inline def getDefaults[T](inline s: Int): Tuple = ${getDefaultsImpl[T]('s)}
def getDefaultsImpl[T](s: Expr[Int])(using Quotes, Type[T]): Expr[Tuple] = {
import quotes.reflect.*
val n = s.asTerm.underlying.asInstanceOf[Literal].constant.value.asInstanceOf[Int]
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
)
def exprOfOption[T](oet: Option[Expr[T]])(using Type[T], Quotes): Expr[Option[T]] = oet match {
case None => Expr(None)
case Some(et) => '{Some($et)}
}
val exprs: List[Option[Expr[Any]]] = terms.map(_.map(_.asExprOf[Any]))
val exprs1: List[Expr[Option[Any]]] = exprs.map(exprOfOption)
Expr.ofTupleFromSeq(exprs1)
}
}
Usage:
val d = summon[Default[Foo]]
summon[d.Out =:= (Option[String], Option[Int], Option[Double])] // compiles
d.defaults // (Some(bar),None,Some(3.141592653589793))
As Dmytro suggests, information is carried in methods <init>default$x of the class companion object.
However, Quotes discourages accessing a symbol's tree in a macro:
https://github.com/lampepfl/dotty/blob/main/library/src/scala/quoted/Quotes.scala#L3628.
Symbol's tree is lost, unless program is compiled with -Yretain-trees)
It is better to let the macro evaluate <init>default$x, rather than copy the right hand side of its definition.
One can do so by expressing terms as :
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.map(Select(Ref(TypeRepr.of[T].typeSymbol.companionModule),_))
)

Scala - Get string representation of object property name, not value, for comparison

I want to be able to get the string representation of an objects property name, not the properties value, so that I can compare it with a variables value inside a conditional statement.
case class CustomObj(name: T)
case class PropertyObj(property: String)
val custObj = CustomObj("Chris")
val propObj = PropertyObj("name")
if(propObj.property.equals(custObj. /* the property name as a String, so "name", not the value ("Chris"*/)) {
// do something
}
How can I access what is essentially the key of the property on the CustomObj?
Try productElementNames like so
case class CustomObj(name: String)
case class PropertyObj(property: String)
val custObj = CustomObj("Chris")
val propObj = PropertyObj("name")
if (custObj.productElementNames.toList.headOption.contains(propObj.property)) { ... } else { ... }
Addressing the comment, based on Krzysztof, try shapeless solution
import shapeless._
import shapeless.ops.record._
def firstPropertyNameOf[P <: Product, L <: HList](p: P)(implicit
gen: LabelledGeneric.Aux[P, L],
toMap: ToMap[L]): Option[String] = {
toMap(gen.to(p)).map{ case (k: Symbol, _) => k.name }.toList.headOption
}
firstPropertyNameOf(custObj).contains(propObj.property) // res1: Boolean = true
I will assume you don't know the type of custObj at compile time. Then you'll have to use runtime reflection in Scala 2.12.
scala> case class CustomObj(name: String)
defined class CustomObj
scala> val custObj: Any = CustomObj("Chris")
custObj: Any = CustomObj(Chris)
scala> import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.currentMirror
scala> val sym = currentMirror.classSymbol(custObj.getClass)
sym: reflect.runtime.universe.ClassSymbol = class CustomObj
scala> val props = sym.info.members.collect{ case m if m.isMethod && m.asMethod.isCaseAccessor => m.name.toString }
props: Iterable[String] = List(name)
scala> if (props.exists(_ == "name")) println("ok")
ok

How to create a random instance of a case class?

Suppose I've got a few case classes, e.g.:
case class C(c1: Int, c2: Double, c3: Option[String])
case class B(b: Int, cs: Seq[C])
case class A(a: String, bs: Seq[B])
Now I would like to generate a few instances of A with random values for tests.
I am looking for a generic way to do that. I can probably do it with runtime reflection but I prefer a compile-time solution.
def randomInstance[A](a: A): A = ???
How can I do it ? Can it be done with shapeless ?
The easiest way for you to do that would be using ScalaCheck. You do so by defining a Gen[A] for your instances:
import org.scalacheck.Gen
final case class C(c1: Int, c2: Double, c3: Option[String])
object C {
val cGen: Gen[C] = for {
c1 <- Gen.posNum[Int]
c2 <- Gen.posNum[Double]
c3 <- Gen.option(Gen.oneOf("foo", "bar", "hello"))
} yield C(c1, c2, c3)
}
And you consume it:
object F {
def main(args: Array[String]): Unit = {
val randomC: C = C.cGen.sample.get
}
}
On top of that, you can add scalacheck-shapeless which generates the Gen[A] for you, with completely random values (where you have no control over them).
You may also want to look into random-data-generator (thanks #Gabriele Petronella), which simplifies things even further. From the docs:
import com.danielasfregola.randomdatagenerator.RandomDataGenerator
object MyApp extends RandomDataGenerator {
case class Example(text: String, n: Int)
val example: Example = random[Example]
// Example(ਈ䈦㈾钜㔪旅ꪔ墛炝푰⡨䌆ᵅ퍧咪, 73967257)
}
This is also especially helpful in property based testing.
We've just moved away from scalacheck-shapeless and use Scala/Java reflection instead.
The main reasons are (1) scalacheck-shapeless uses Macros (slow compilation), (2) the API is a bit more verbose than my liking, and (3) the generated values are way too wild (e.g. generating strings with Japanese characters).
However, setting it up is a bit more involved. Here is a full working code that you can copy into your codebase:
import scala.reflect.api
import scala.reflect.api.{TypeCreator, Universe}
import scala.reflect.runtime.universe._
object Maker {
val mirror = runtimeMirror(getClass.getClassLoader)
var makerRunNumber = 1
def apply[T: TypeTag]: T = {
val method = typeOf[T].companion.decl(TermName("apply")).asMethod
val params = method.paramLists.head
val args = params.map { param =>
makerRunNumber += 1
param.info match {
case t if t <:< typeOf[Enumeration#Value] => chooseEnumValue(convert(t).asInstanceOf[TypeTag[_ <: Enumeration]])
case t if t =:= typeOf[Int] => makerRunNumber
case t if t =:= typeOf[Long] => makerRunNumber
case t if t =:= typeOf[Date] => new Date(Time.now.inMillis)
case t if t <:< typeOf[Option[_]] => None
case t if t =:= typeOf[String] && param.name.decodedName.toString.toLowerCase.contains("email") => s"random-$arbitrary#give.asia"
case t if t =:= typeOf[String] => s"arbitrary-$makerRunNumber"
case t if t =:= typeOf[Boolean] => false
case t if t <:< typeOf[Seq[_]] => List.empty
case t if t <:< typeOf[Map[_, _]] => Map.empty
// Add more special cases here.
case t if isCaseClass(t) => apply(convert(t))
case t => throw new Exception(s"Maker doesn't support generating $t")
}
}
val obj = mirror.reflectModule(typeOf[T].typeSymbol.companion.asModule).instance
mirror.reflect(obj).reflectMethod(method)(args:_*).asInstanceOf[T]
}
def chooseEnumValue[E <: Enumeration: TypeTag]: E#Value = {
val parentType = typeOf[E].asInstanceOf[TypeRef].pre
val valuesMethod = parentType.baseType(typeOf[Enumeration].typeSymbol).decl(TermName("values")).asMethod
val obj = mirror.reflectModule(parentType.termSymbol.asModule).instance
mirror.reflect(obj).reflectMethod(valuesMethod)().asInstanceOf[E#ValueSet].head
}
def convert(tpe: Type): TypeTag[_] = {
TypeTag.apply(
runtimeMirror(getClass.getClassLoader),
new TypeCreator {
override def apply[U <: Universe with Singleton](m: api.Mirror[U]) = {
tpe.asInstanceOf[U # Type]
}
}
)
}
def isCaseClass(t: Type) = {
t.companion.decls.exists(_.name.decodedName.toString == "apply") &&
t.decls.exists(_.name.decodedName.toString == "copy")
}
}
And, when you want to use it, you can call:
val user = Maker[User]
val user2 = Maker[User].copy(email = "someemail#email.com")
The code above generates arbitrary and unique values. They aren't exactly randomised. It's best for using in tests.
Read our full blog post here: https://give.engineering/2018/08/24/instantiate-case-class-with-arbitrary-value.html
We've started using Magnolia, which provides a faster type class derivation compared to shapeless for derivation of Arbitrary instances.
Here is the library to use, and here is an example (docs):
case class Inner(int: Int, str: String)
case class Outer(inner: Inner)
// ScalaCheck Arbitrary
import magnolify.scalacheck.auto._
import org.scalacheck._ // implicit instances for Arbitrary[Int], etc.
val arb: Arbitrary[Outer] = implicitly[Arbitrary[Outer]]
arb.arbitrary.sample
// = Some(Outer(Inter(12345, abcde)))

Merge two case class of same type, except some fields

If you have a case class like:
case class Foo(x: String, y: String, z: String)
And you have two instances like:
Foo("x1","y1","z1")
Foo("x2","y2","z2")
Is it possible to merge instance 1 in instance 2, except for field z, so that the result would be:
Foo("x1","y1","z2")
My usecase is just that I give JSON objects to a Backbone app through a Scala API, and the Backbone app gives me back a JSON of the same structure so that I can save/update it. These JSON objects are parsed as case class for easy Scala manipulation. But some fields should never be updated by the client side (like creationDate). For now I'm doing a manual merge but I'd like a more generic solution, a bit like an enhanced copy function.
What I'd like is something like this:
instanceFromDB.updateWith(instanceFromBackbone, excludeFields = "creationDate" )
But I'd like it to be typesafe :)
Edit:
My case class have a lot more fields and I'd like the default bevavior to merge fields unless I explicitly say to not merge them.
What you want is already there; you just need to approach the problem the other way.
case class Bar(x: String, y: String)
val b1 = Bar("old", "tired")
val b2 = Bar("new", "fresh")
If you want everything in b2 not specifically mentioned, you should copy from b2; anything from b1 you want to keep you can mention explicitly:
def keepY(b1: Bar, b2: Bar) = b2.copy(y = b1.y)
scala> keepY(b1, b2)
res1: Bar = Bar(new,tired)
As long as you are copying between two instances of the same case class, and the fields are immutable like they are by default, this will do what you want.
case class Foo(x: String, y: String, z: String)
Foo("old_x", "old_y", "old_z")
// res0: Foo = Foo(old_x,old_y,old_z)
Foo("new_x", "new_y", "new_z")
// res1: Foo = Foo(new_x,new_y,new_z)
// use copy() ...
res0.copy(res1.x, res1.y)
// res2: Foo = Foo(new_x,new_y,old_z)
// ... with by-name parameters
res0.copy(y = res1.y)
// res3: Foo = Foo(old_x,new_y,old_z)
You can exclude class params from automatic copying by the copy method by currying:
case class Person(name: String, age: Int)(val create: Long, val id: Int)
This makes it clear which are ordinary value fields which the client sets and which are special fields. You can't accidentally forget to supply a special field.
For the use case of taking the value fields from one instance and the special fields from another, by reflectively invoking copy with either default args or the special members of the original:
import scala.reflect._
import scala.reflect.runtime.{ currentMirror => cm }
import scala.reflect.runtime.universe._
import System.{ currentTimeMillis => now }
case class Person(name: String, age: Int = 18)(val create: Long = now, val id: Int = Person.nextId) {
require(name != null)
require(age >= 18)
}
object Person {
private val ns = new java.util.concurrent.atomic.AtomicInteger
def nextId = ns.getAndIncrement()
}
object Test extends App {
/** Copy of value with non-defaulting args from model. */
implicit class Copier[A: ClassTag : TypeTag](val value: A) {
def copyFrom(model: A): A = {
val valueMirror = cm reflect value
val modelMirror = cm reflect model
val name = "copy"
val copy = (typeOf[A] member TermName(name)).asMethod
// either defarg or default val for type of p
def valueFor(p: Symbol, i: Int): Any = {
val defarg = typeOf[A] member TermName(s"$name$$default$$${i+1}")
if (defarg != NoSymbol) {
println(s"default $defarg")
(valueMirror reflectMethod defarg.asMethod)()
} else {
println(s"def val for $p")
val pmethod = typeOf[A] member p.name
if (pmethod != NoSymbol) (modelMirror reflectMethod pmethod.asMethod)()
else throw new RuntimeException("No $p on model")
}
}
val args = (for (ps <- copy.paramss; p <- ps) yield p).zipWithIndex map (p => valueFor(p._1,p._2))
(valueMirror reflectMethod copy)(args: _*).asInstanceOf[A]
}
}
val customer = Person("Bob")()
val updated = Person("Bobby", 37)(id = -1)
val merged = updated.copyFrom(customer)
assert(merged.create == customer.create)
assert(merged.id == customer.id)
}
case class Foo(x: String, y: String, z: String)
val foo1 = Foo("x1", "y1", "z1")
val foo2 = Foo("x2", "y2", "z2")
val mergedFoo = foo1.copy(z = foo2.z) // Foo("x1", "y1", "z2")
If you change Foo later to:
case class Foo(w: String, x: String, y: String, z: String)
No modification will have to be done. Explicitly:
val foo1 = Foo("w1", "x1", "y1", "z1")
val foo2 = Foo("w2", "x2", "y2", "z2")
val mergedFoo = foo1.copy(z = foo2.z) // Foo("w1", "x1", "y1", "z2")