I am coding using scala3, leveraging programmatic structural types.
The structural types happen to mimic existing case classes:
their definition is pure boiler plate,
hence the temptation to craft them through meta-programming.
I understand how to craft a function implementation, typically via typeclass derivation.
But here we are trying to craft a (structural) type.
This was possible in scala2, via class macro annotation, but those are gone in scala3.
Is there a way ? If so how ?
Code below is the result I would like to obtain :
// Library part
trait View extends scala.Selectable :
def selectDynamic(key:String) =
println(s"$key is being looked up")
???
// DSL Definition part
case class SomeDefWithInt ( i : Int )
case class SomeDefWithString( s : String )
// Boiler-plate code
type ViewOf[M] = M match
case SomeDefWithInt => View { def i : Int }
case SomeDefWithString => View { def s : String }
// Mockup usage
class V extends View
val v = V()
v.asInstanceOf[ViewOf[SomeDefWithInt ]].i
v.asInstanceOf[ViewOf[SomeDefWithString]].s
is it possible to create ViewOf[M] of an arbitrary case class M ?
Thank you !
Just in case, here is what I meant by hiding ViewOf inside a type class (type classes is an alternative to match types). Sadly, in Scala 3 this is wordy.
(version 1)
import scala.annotation.experimental
import scala.quoted.{Expr, Quotes, Type, quotes}
// Library part
trait View extends Selectable {
def applyDynamic(key: String)(args: Any*): Any = {
println(s"$key is being looked up with $args")
if (key == "i") 1
else if (key == "s") "a"
else ???
}
def selectDynamic(key: String): Any = {
println(s"$key is being looked up")
if (key == "i1") 2
else if (key == "s1") "b"
else ???
}
}
// type class
trait ViewOf[M <: Product] {
type Out <: View
}
object ViewOf {
transparent inline given mkViewOf[M <: Product]: ViewOf[M] = ${givenImpl[M]}
#experimental // because .newClass is #experimental
def givenImpl[M <: Product : Type](using Quotes): Expr[ViewOf[M]] = {
import quotes.reflect.*
extension (symb: Symbol) {
def setFlags(flags: Flags): Symbol = {
given dotty.tools.dotc.core.Contexts.Context =
quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
symb.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol]
.denot.setFlag(flags.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet])
symb
}
}
def newType(cls: Symbol, name: String, tpe: TypeRepr, flags: Flags = Flags.EmptyFlags, privateWithin: Symbol = Symbol.noSymbol): Symbol = {
given dotty.tools.dotc.core.Contexts.Context =
quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
import dotty.tools.dotc.core.Decorators.toTypeName
dotty.tools.dotc.core.Symbols.newSymbol(
cls.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol],
name.toTypeName,
flags.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet],
tpe.asInstanceOf[dotty.tools.dotc.core.Types.Type],
privateWithin.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol]
).asInstanceOf[Symbol]
}
val M = TypeRepr.of[M]
val fields = M.typeSymbol.caseFields
val viewImplDecls = (cls: Symbol) =>
fields.flatMap(fieldSymb =>
Seq(
Symbol.newMethod(cls, fieldSymb.name, MethodType(Nil)(_ => Nil, _ => M.memberType(fieldSymb)), // vararg? MatchError: Inlined
Flags.Deferred, privateWithin = Symbol.noSymbol),
Symbol.newVal(cls, fieldSymb.name + "1", M.memberType(fieldSymb),
Flags.Deferred, privateWithin = Symbol.noSymbol)
)
)
val viewImplParents = List(TypeTree.of[AnyRef], TypeTree.of[View])
val viewImplCls = Symbol.newClass(Symbol.spliceOwner, "ViewImpl", viewImplParents.map(_.tpe), viewImplDecls, selfType = None)
.setFlags(Flags.Trait)
val methodDefs = fields.flatMap(fieldSymb => {
val methodSymb = viewImplCls.declaredMethod(fieldSymb.name).head
val valSymb = viewImplCls.fieldMember(fieldSymb.name + "1")
Seq(
DefDef(methodSymb, _ => None),
ValDef(valSymb, None)
)
})
val viewImplClsDef = ClassDef(viewImplCls, viewImplParents, body = methodDefs)
val viewOfImplDecls = (cls: Symbol) => List(newType(cls, "Out",
TypeBounds(viewImplCls.typeRef, viewImplCls.typeRef), Flags.Override))
val viewOfTypeTree = TypeTree.of[ViewOf[M]]
val viewOfImplParents = List(TypeTree.of[AnyRef], viewOfTypeTree)
val viewOfImplCls = Symbol.newClass(Symbol.spliceOwner, "ViewOfImpl", viewOfImplParents.map(_.tpe), viewOfImplDecls, selfType = None)
val outSymb = viewOfImplCls.declaredType("Out").head
val outTypeDef = TypeDef(outSymb)
val viewOfImplClsDef = ClassDef(viewOfImplCls, viewOfImplParents, body = List(outTypeDef))
val newViewOfImpl = Apply(Select(New(TypeIdent(viewOfImplCls)), viewOfImplCls.primaryConstructor), Nil)
val res = Block(List(viewImplClsDef, viewOfImplClsDef), newViewOfImpl).asExprOf[ViewOf[M]]
println(res.show + "=" + res.asTerm.show(using Printer.TreeStructure))
res
}
}
extension (v: View) {
def refine[M <: Product](using viewOf: ViewOf[M]): viewOf.Out = v.asInstanceOf[viewOf.Out]
}
// DSL Definition part
case class SomeDefWithInt ( i : Int )
case class SomeDefWithString( s : String )
// Mockup usage
class V extends View
val v = V()
println(v.refine[SomeDefWithInt].i())
// i is being looked up with ArraySeq()
// 1
println(v.refine[SomeDefWithString].s())
// s is being looked up with ArraySeq()
// a
println(v.refine[SomeDefWithInt].i1)
// i1 is being looked up
// 2
println(v.refine[SomeDefWithString].s1)
// s1 is being looked up
// b
//scalac: {
// trait ViewImpl extends java.lang.Object with Macros.View {
// def i(): scala.Int
// val i1: scala.Int
// }
// class ViewOfImpl extends java.lang.Object with Macros.ViewOf[App.SomeDefWithInt] {
// type Out // actually, type Out = ViewImpl
// }
// new ViewOfImpl()
//}=Block(List(ClassDef("ViewImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(DefDef("i", List(TermParamClause(Nil)), Inferred(), None), ValDef("i1", Inferred(), None))), ClassDef("ViewOfImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(TypeDef("Out", TypeBoundsTree(Inferred(), Inferred()))))), Apply(Select(New(Inferred()), "<init>"), Nil))
//scalac: {
// trait ViewImpl extends java.lang.Object with Macros.View {
// def s(): scala.Predef.String
// val s1: scala.Predef.String
// }
// class ViewOfImpl extends java.lang.Object with Macros.ViewOf[App.SomeDefWithString] {
// type Out // actually, type Out = ViewImpl
// }
// new ViewOfImpl()
//}=Block(List(ClassDef("ViewImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(DefDef("s", List(TermParamClause(Nil)), Inferred(), None), ValDef("s1", Inferred(), None))), ClassDef("ViewOfImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(TypeDef("Out", TypeBoundsTree(Inferred(), Inferred()))))), Apply(Select(New(Inferred()), "<init>"), Nil))
ViewOf[M] is meant is to be used by a DSL user, so no way to hide it within a derived type class.
Not sure I understood.
Method Override with Scala 3 Macros
`tq` equivalent in Scala 3 macros
How to generate a class in Dotty with macro?
How to splice multiple expressions in quote syntax of scala 3 macros?
How to access parameter list of case class in a dotty macro
https://github.com/lampepfl/dotty/discussions/14056
Another implementation of the type class (with a refinement type instead of trait type)
(version 2)
trait ViewOf[M <: Product] {
type Out <: View
}
object ViewOf {
transparent inline given mkViewOf[M <: Product]: ViewOf[M] = ${givenImpl[M]}
#experimental // because .newClass is #experimental
def givenImpl[M <: Product : Type](using Quotes): Expr[ViewOf[M]] = {
import quotes.reflect.*
def makeRefinement(parent: TypeRepr, names: List[String], infos: List[TypeRepr]): TypeRepr =
names.zip(infos).foldLeft(parent){ case (acc, (name, tpe)) => Refinement(acc, name, tpe) }
def newType(cls: Symbol, name: String, tpe: TypeRepr, flags: Flags = Flags.EmptyFlags, privateWithin: Symbol = Symbol.noSymbol): Symbol = {
given dotty.tools.dotc.core.Contexts.Context =
quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
import dotty.tools.dotc.core.Decorators.toTypeName
dotty.tools.dotc.core.Symbols.newSymbol(
cls.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol],
name.toTypeName,
flags.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet],
tpe.asInstanceOf[dotty.tools.dotc.core.Types.Type],
privateWithin.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol]
).asInstanceOf[Symbol]
}
val M = TypeRepr.of[M]
val fields = M.typeSymbol.caseFields
val fieldNames = fields.flatMap(fieldSymb => Seq(fieldSymb.name, fieldSymb.name + "1"))
val fieldMethodTypes = fields.flatMap(fieldSymb => Seq(
MethodType(List("args"))(_ => List(AnnotatedType(TypeRepr.of[Any], '{new scala.annotation.internal.Repeated()}.asTerm)), _ => M.memberType(fieldSymb)),
ByNameType(M.memberType(fieldSymb)))
)
val refinement = makeRefinement(TypeRepr.of[View], fieldNames, fieldMethodTypes)
val viewOfImplDecls = (cls: Symbol) => List(newType(cls, "Out",
TypeBounds(refinement, refinement), Flags.Override))
val viewOfTypeTree = TypeTree.of[ViewOf[M]]
val viewOfImplParents = List(TypeTree.of[AnyRef], viewOfTypeTree)
val viewOfImplCls = Symbol.newClass(Symbol.spliceOwner, "ViewOfImpl", viewOfImplParents.map(_.tpe), viewOfImplDecls, selfType = None)
val outSymb = viewOfImplCls.declaredType("Out").head
val outTypeDef = TypeDef(outSymb)
val viewOfImplClsDef = ClassDef(viewOfImplCls, viewOfImplParents, body = List(outTypeDef))
val newViewOfImpl = Apply(Select(New(TypeIdent(viewOfImplCls)), viewOfImplCls.primaryConstructor), Nil)
val res = Block(List(viewOfImplClsDef), newViewOfImpl).asExprOf[ViewOf[M]]
println(res.show + "=" + res.asTerm.show(using Printer.TreeStructure))
res
}
}
println(v.refine[SomeDefWithInt].i(10, "x", true))
//i is being looked up with ArraySeq((10,x,true))
//1
println(v.refine[SomeDefWithString].s(20, "y", 30L))
//s is being looked up with ArraySeq((20,y,30))
//a
println(v.refine[SomeDefWithInt].i1)
//i1 is being looked up
//2
println(v.refine[SomeDefWithString].s1)
//s1 is being looked up
//b
//scalac: {
// class ViewOfImpl extends java.lang.Object with Macros.ViewOf[App.SomeDefWithInt] {
// type Out // actually, type Out = View {def i(args: Any*): Int; def i1: Int}
// }
// new ViewOfImpl()
//}=Block(List(ClassDef("ViewOfImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(TypeDef("Out", TypeBoundsTree(Inferred(), Inferred()))))), Apply(Select(New(Inferred()), "<init>"), Nil))
Also we can use Mirror instead of reflection
(version 3)
trait ViewOf[M <: Product] {
type Out <: View
}
object ViewOf {
transparent inline given mkViewOf[M <: Product]: ViewOf[M] = ${givenImpl[M]}
def givenImpl[M <: Product : Type](using Quotes): Expr[ViewOf[M]] = {
import quotes.reflect.*
def makeRefinement(parent: TypeRepr, namesAndTypes: List[(String, TypeRepr)]): TypeRepr =
namesAndTypes.foldLeft(parent) { case (acc, (name, tpe)) => Refinement(acc, name, tpe) }
def mkNamesAndTypes[mels: Type, mets: Type]: List[(String, TypeRepr)] =
(Type.of[mels], Type.of[mets]) match {
case ('[EmptyTuple], '[EmptyTuple]) => Nil
case ('[mel *: melTail], '[met *: metTail] ) => {
val name = Type.valueOfConstant[mel].get.toString
val name1 = name + "1"
//scala.MatchError: Inlined(Ident(Macros$),List(),Apply(Select(New(Select(Select(Select(Ident(scala),annotation),internal),Repeated)),<init>),List())) (of class dotty.tools.dotc.ast.Trees$Inlined)
//val methodType = MethodType(List("args"))(_ => List(AnnotatedType(TypeRepr.of[Any], '{new scala.annotation.internal.Repeated()}.asTerm)), _ => TypeRepr.of[met])
val methodType = MethodType(Nil)(_ => Nil, _ => TypeRepr.of[met])
val methodType1 = ByNameType(TypeRepr.of[met])
(name, methodType) :: (name1, methodType1) :: mkNamesAndTypes[melTail, metTail]
}
}
val namesAndTypes = Expr.summon[Mirror.ProductOf[M]].get match {
case '{ $m: Mirror.ProductOf[M] { type MirroredElemLabels = mels; type MirroredElemTypes = mets } } =>
mkNamesAndTypes[mels, mets]
}
val res = makeRefinement(TypeRepr.of[View], namesAndTypes).asType match {
case '[tpe] =>
'{
new ViewOf[M] {
type Out = tpe
}
}
}
println(res.show)
res
}
}
Unfortunately, this doesn't work because of an extra type ascription (Expr looses type refinement)
//scalac: {
// final class $anon() extends Macros.ViewOf[App.SomeDefWithInt] {
// type Out = Macros.View {
// def i(): scala.Int
// def i1: scala.Int
// }
// }
//
// (new $anon(): Macros.ViewOf[App.SomeDefWithInt]) // <--- HERE!!!
//}
https://github.com/lampepfl/dotty/issues/15566 (for structural refinements i.e. defs, their loss seems to be expected behavior, but type refinement loss can be a bug)
So, at least once we have to use low-level newClass to avoid type ascription
(version 4)
trait ViewOf[M <: Product] {
type Out <: View
}
object ViewOf {
transparent inline given mkViewOf[M <: Product]: ViewOf[M] = ${givenImpl[M]}
#experimental // because .newClass is #experimental
def givenImpl[M <: Product : Type](using Quotes): Expr[ViewOf[M]] = {
import quotes.reflect.*
def makeRefinement(parent: TypeRepr, namesAndTypes: List[(String, TypeRepr)]): TypeRepr =
namesAndTypes.foldLeft(parent) { case (acc, (name, tpe)) => Refinement(acc, name, tpe) }
def mkNamesAndTypes[mels: Type, mets: Type]: List[(String, TypeRepr)] =
(Type.of[mels], Type.of[mets]) match {
case ('[EmptyTuple], '[EmptyTuple]) => Nil
case ('[mel *: melTail], '[met *: metTail] ) => {
val name = Type.valueOfConstant[mel].get.toString
val name1 = name + "1"
val methodType = MethodType(List("args"))(_ => List(AnnotatedType(TypeRepr.of[Any], '{new scala.annotation.internal.Repeated()}.asTerm)), _ => TypeRepr.of[met])
val methodType1 = ByNameType(TypeRepr.of[met])
(name, methodType) :: (name1, methodType1) :: mkNamesAndTypes[melTail, metTail]
}
}
val namesAndTypes = Expr.summon[Mirror.ProductOf[M]].get match {
case '{ $m: Mirror.ProductOf[M] { type MirroredElemLabels = mels; type MirroredElemTypes = mets } } =>
mkNamesAndTypes[mels, mets]
}
val refinement = makeRefinement(TypeRepr.of[View], namesAndTypes)
def newType(cls: Symbol, name: String, tpe: TypeRepr, flags: Flags = Flags.EmptyFlags, privateWithin: Symbol = Symbol.noSymbol): Symbol = {
given dotty.tools.dotc.core.Contexts.Context =
quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
import dotty.tools.dotc.core.Decorators.toTypeName
dotty.tools.dotc.core.Symbols.newSymbol(
cls.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol],
name.toTypeName,
flags.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet],
tpe.asInstanceOf[dotty.tools.dotc.core.Types.Type],
privateWithin.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol]
).asInstanceOf[Symbol]
}
val viewOfImplDecls = (cls: Symbol) => List(newType(cls, "Out",
TypeBounds(refinement, refinement),
Flags.Override))
val viewOfTypeTree = TypeTree.of[ViewOf[M]]
val viewOfImplParents = List(TypeTree.of[AnyRef], viewOfTypeTree)
val viewOfImplCls = Symbol.newClass(Symbol.spliceOwner, "ViewOfImpl", viewOfImplParents.map(_.tpe), viewOfImplDecls, selfType = None)
val outSymb = viewOfImplCls.declaredType("Out").head
val outTypeDef = TypeDef(outSymb)
val viewOfImplClsDef = ClassDef(viewOfImplCls, viewOfImplParents, body = List(outTypeDef))
// this would be an extra type ascription to be avoided
// val newViewOfImpl = Typed(Apply(Select(New(TypeIdent(viewOfImplCls)), viewOfImplCls.primaryConstructor), Nil), TypeTree.of[ViewOf[M]])
val newViewOfImpl = Apply(Select(New(TypeIdent(viewOfImplCls)), viewOfImplCls.primaryConstructor), Nil)
val res = Block(List(viewOfImplClsDef), newViewOfImpl).asExprOf[ViewOf[M]]
println(res.show + "=" + res.asTerm.show(using Printer.TreeStructure))
res
}
}
I have code where a class can provide modified copies of itself, like so:
case class A(i: Int, s: String) {
def foo(ii: Int): A = copy(i = ii)
def bar(ss: String): A = copy(s = ss)
}
I want to create a function that takes some optional arguments and creates these modified copies using these arguments if they are defined:
def subA(a: A, oi: Option[Int] = None, os: Option[String] = None): A = {
if (oi.isDefined && os.isDefined)
a.foo(oi.get).bar(os.get)
else if (oi.isDefined && !os.isDefined)
a.foo(oi.get)
else if (!oi.isDefined && os.isDefined)
a.bar(os.get)
else
a
}
This is clearly not sustainable, as I add new optional arguments, I have to create cases for every combination of arguments...
I also cannot do:
a.foo(oi.getOrElse(a.i)).bar(os.getOrElse(a.s))
Because in my actual code, if oi or os is not provided, I should NOT run their associated foo and bar functions. In other words, I have no default arguments for oi and os, rather their existence defines whether I should run certain functions at all.
Current solution, extend the class:
implicit class A_extended(a: A) {
def fooOption(oi: Option[Int]): A = if (oi.isDefined) a.foo(oi.get) else a
def barOption(os: Option[String]): A = if (os.isDefined) a.bar(os.get) else a
}
def subA(a: A, oi: Option[Int] = None, os: Option[String] = None): A = {
a.fooOption(oi).barOption(os)
}
But this problem comes up often and it's a bit tedious to do this constantly, is there something like:
// oi: Option[Int], foo: Int => A
oi.ifDefinedThen(a.foo(_), a) // returns a.foo(oi.get) if oi is not None, else just a
Or should I just extend Option to provide this functionality?
Use fold on option final def fold[B](ifEmpty: => B)(f: A => B): B
def subA(a: A, oi: Option[Int] = None, os: Option[String] = None): A = {
val oia = oi.fold(a)(a.foo)
os.fold(oia)(oia.bar)
}
Scala REPL
scala> def subA(a: A, oi: Option[Int] = None, os: Option[String] = None): A = {
val oia = oi.fold(a)(a.foo)
os.fold(oia)(oia.bar)
}
defined function subA
scala> subA(A(1, "bow"), Some(2), Some("cow"))
res10: A = A(2, "cow")
or
Use pattern matching to deal with options elegantly. Create a tuple of options and then use pattern matching to extract the inner values
val a = Some(1)
val b = Some("some string")
(a, b) match {
case (Some(x), Some(y)) =>
case (Some(x), _) =>
case (_, Some(y)) =>
case (_, _) =>
}
Well... You can use reflection to create arbitrary copiers and even updaters for your case classes.
The difference is that an updater updates the case class instance and the copier create a new copy with updated fields.
An implementation of an updater can be done as below,
import scala.language.existentials
import scala.reflect.runtime.{universe => ru}
def copyInstance[C: scala.reflect.ClassTag](instance: C, mapOfUpdates: Map[String, T forSome {type T}]): C = {
val runtimeMirror = ru.runtimeMirror(instance.getClass.getClassLoader)
val instanceMirror = runtimeMirror.reflect(instance)
val tpe = instanceMirror.symbol.toType
val copyMethod = tpe.decl(ru.TermName("copy")).asMethod
val copyMethodInstance = instanceMirror.reflectMethod(copyMethod)
val updates = tpe.members
.filter(member => member.asTerm.isCaseAccessor && member.asTerm.isMethod)
.map(member => {
val term = member.asTerm
//check if we need to update it or use the instance value
val updatedValue = mapOfUpdates.getOrElse(
key = term.name.toString,
default = instanceMirror.reflectField(term).get
)
updatedValue
}).toSeq.reverse
val copyOfInstance = copyMethodInstance(updates: _*).asInstanceOf[C]
copyOfInstance
}
def updateInstance[C: scala.reflect.ClassTag](instance: C, mapOfUpdates: Map[String, T forSome {type T}]): C = {
val runtimeMirror = ru.runtimeMirror(instance.getClass.getClassLoader)
val instanceMirror = runtimeMirror.reflect(instance)
val tpe = instanceMirror.symbol.toType
tpe.members.foreach(member => {
val term = member.asTerm
term.isCaseAccessor && term.isMethod match {
case true =>
// it is a case class accessor, check if we need to update it
mapOfUpdates.get(term.name.toString).foreach(updatedValue => {
val fieldMirror = instanceMirror.reflectField(term.accessed.asTerm)
// filed mirrors can even update immutable fields !!
fieldMirror.set(updatedValue)
})
case false => // Not a case class accessor, do nothing
}
})
instance
}
And since you wanted to use Options to copy, here is your define once and use with all case classes copyUsingOptions
def copyUsingOptions[C: scala.reflect.ClassTag](instance: C, listOfUpdateOptions: List[Option[T forSome {type T}]]): C = {
val runtimeMirror = ru.runtimeMirror(instance.getClass.getClassLoader)
val instanceMirror = runtimeMirror.reflect(instance)
val tpe = instanceMirror.symbol.toType
val copyMethod = tpe.decl(ru.TermName("copy")).asMethod
val copyMethodInstance = instanceMirror.reflectMethod(copyMethod)
val updates = tpe.members.toSeq
.filter(member => member.asTerm.isCaseAccessor && member.asTerm.isMethod)
.reverse
.zipWithIndex
.map({ case (member, index) =>
listOfUpdateOptions(index).getOrElse(instanceMirror.reflectField(member.asTerm).get)
})
val copyOfInstance = copyMethodInstance(updates: _*).asInstanceOf[C]
copyOfInstance
}
Now you can use these updateInstance or copyInstance to update or copy instances of any case classes,
case class Demo(id: Int, name: String, alliance: Option[String], power: Double, lat: Double, long: Double)
// defined class Demo
val d1 = Demo(1, "player_1", None, 15.5, 78.404, 71.404)
// d1: Demo = Demo(1,player_1,None,15.5,78.404,71.404)
val d1WithAlliance = copyInstance(d1, Map("alliance" -> Some("Empires")))
// d1WithAlliance: Demo = Demo(1,player_1,Some(Empires),15.5,78.404,71.404)
val d2 = copyInstance(d1, Map("id" -> 2, "name" -> "player_2"))
d2: Demo = Demo(2,player_2,None,15.5,78.404,71.404)
val d3 = copyWithOptions(
d1, List(Some(3),
Some("player_3"), Some(Some("Vikings")), None, None, None)
)
// d3: Demo = Demo(3,player_3,Some(Vikings),15.5,78.404,71.404)
// Or you can update instance using updateInstance
val d4 = updateInstance(d1, Map("id" -> 4, "name" -> "player_4"))
// d4: Demo = Demo(4,player_4,None,15.5,78.404,71.404)
d1
// d1: Demo = Demo(4,player_4,None,15.5,78.404,71.404)
Another option (no pun intended, heh) would be to have foo and bar themselves take and fold over Options:
case class A(i: Int, s: String) {
def foo(optI: Option[Int]): A =
optI.fold(this)(ii => copy(i = ii))
def bar(optS: Option[String]): A =
optS.fold(this)(ss => copy(s = ss))
}
Then, subA can be minimal:
object A {
def subA(
a: A,
optI: Option[Int] = None,
optS: Option[String] = None): A =
a foo optI bar optS
}
You can also overload foo and bar to take plain Int and String as well if you have to maintain the API; in that case make the Option-taking methods call out to their corresponding non-Option-taking ones.
I need to be able to instantiate various case classes through reflection, both by figuring out the argument types of the constructor, as well as invoking the constructor with all default arguments.
I've come as far as this:
import reflect.runtime.{universe => ru}
val m = ru.runtimeMirror(getClass.getClassLoader)
case class Bar(i: Int = 33)
val tpe = ru.typeOf[Bar]
val classBar = tpe.typeSymbol.asClass
val cm = m.reflectClass(classBar)
val ctor = tpe.declaration(ru.nme.CONSTRUCTOR).asMethod
val ctorm = cm.reflectConstructor(ctor)
// figuring out arg types
val arg1 = ctor.paramss.head.head
arg1.typeSignature =:= ru.typeOf[Int] // true
// etc.
// instantiating with given args
val p = ctorm(33)
Now the missing part:
val p2 = ctorm() // IllegalArgumentException: wrong number of arguments
So how can I create p2 with the default arguments of Bar, i.e. what would be Bar() without reflection.
So in the linked question, the :power REPL uses internal API, which means that defaultGetterName is not available, so we need to construct that from hand. An adoption from #som-snytt 's answer:
def newDefault[A](implicit t: reflect.ClassTag[A]): A = {
import reflect.runtime.{universe => ru, currentMirror => cm}
val clazz = cm.classSymbol(t.runtimeClass)
val mod = clazz.companionSymbol.asModule
val im = cm.reflect(cm.reflectModule(mod).instance)
val ts = im.symbol.typeSignature
val mApply = ts.member(ru.newTermName("apply")).asMethod
val syms = mApply.paramss.flatten
val args = syms.zipWithIndex.map { case (p, i) =>
val mDef = ts.member(ru.newTermName(s"apply$$default$$${i+1}")).asMethod
im.reflectMethod(mDef)()
}
im.reflectMethod(mApply)(args: _*).asInstanceOf[A]
}
case class Foo(bar: Int = 33)
val f = newDefault[Foo] // ok
Is this really the shortest path?
Not minimized... and not endorsing...
scala> import scala.reflect.runtime.universe
import scala.reflect.runtime.universe
scala> import scala.reflect.internal.{ Definitions, SymbolTable, StdNames }
import scala.reflect.internal.{Definitions, SymbolTable, StdNames}
scala> val ds = universe.asInstanceOf[Definitions with SymbolTable with StdNames]
ds: scala.reflect.internal.Definitions with scala.reflect.internal.SymbolTable with scala.reflect.internal.StdNames = scala.reflect.runtime.JavaUniverse#52a16a10
scala> val n = ds.newTermName("foo")
n: ds.TermName = foo
scala> ds.nme.defaultGetterName(n,1)
res1: ds.TermName = foo$default$1
Here's a working version 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. The data aren't exactly randomised. It's best for using in tests.
It works with Enum and nested case class. You can also easily extend it to support some other special types.
Read our full blog post here: https://give.engineering/2018/08/24/instantiate-case-class-with-arbitrary-value.html
This is the most complete example how to create case class via reflection with default constructor parameters(Github source):
import scala.reflect.runtime.universe
import scala.reflect.internal.{Definitions, SymbolTable, StdNames}
object Main {
def newInstanceWithDefaultParameters(className: String): Any = {
val runtimeMirror: universe.Mirror = universe.runtimeMirror(getClass.getClassLoader)
val ds = universe.asInstanceOf[Definitions with SymbolTable with StdNames]
val classSymbol = runtimeMirror.staticClass(className)
val classMirror = runtimeMirror.reflectClass(classSymbol)
val moduleSymbol = runtimeMirror.staticModule(className)
val moduleMirror = runtimeMirror.reflectModule(moduleSymbol)
val moduleInstanceMirror = runtimeMirror.reflect(moduleMirror.instance)
val defaultValueMethodSymbols = moduleMirror.symbol.info.members
.filter(_.name.toString.startsWith(ds.nme.defaultGetterName(ds.newTermName("apply"), 1).toString.dropRight(1)))
.toSeq
.reverse
.map(_.asMethod)
val defaultValueMethods = defaultValueMethodSymbols.map(moduleInstanceMirror.reflectMethod).toList
val primaryConstructorMirror = classMirror.reflectConstructor(classSymbol.primaryConstructor.asMethod)
primaryConstructorMirror.apply(defaultValueMethods.map(_.apply()): _*)
}
def main(args: Array[String]): Unit = {
val instance = newInstanceWithDefaultParameters(classOf[Bar].getName)
println(instance)
}
}
case class Bar(i: Int = 33)