I am building a DSL in Scala, and for that, I need to store "instances" of a class (Parent in this case), except these "instances" must be re-created several times at runtime. So instead I am storing "constructor functions" - a lambda that makes the instance.
consider the following code - imagine that userVar can change at runtime and the updated value must be used when constructing the instances.
class Parent {
def func(param: Any): Unit = { ... }
}
class User {
def construct(constr: => Parent): ParentWrapper = { ... }
var userVar = 13
construct(new Parent {
func(1)
func(userVar)
}
construct(new Parent {
func(userVar)
}
}
A more natural way of expressing what I want would be this (using the above definitions):
class User {
var userVar = 13
object ObjectA extends Parent {
func(1)
func(userVar)
}
construct(ObjectA)
}
However, that seems impossible, given that ObjectA is created immediately, and doesn't have a "constructor".
I am thinking, with some creative use of macros, I could instead do this:
class User {
var userVar = 13
constructMacro {
func(1)
func(userVar}
}
}
and have the constructMacro convert the code to construct(new Parent {code block goes here}).
How would I do that?
Or is there a better way to avoid the awkward construct(new Parent{...}) call? My requirement is that somewhere in the User class a reference is stored that I can repeatedly call and get new instances of the Parent definition that reflect new values used in their construction -- and the construct call should ideally return a wrapper object for that reference.
Unfortunately macros will not help.
Macro annotations (which expand before type checking) can't annotate code blocks:
#constructMacro {
func(1)
func(userVar)
}
is illegal.
Def macros (which expand during type checking) have their arguments type checked before macros are expanded. So
constructMacro {
func(1)
func(userVar)
}
doesn't compile:
Error: not found: value func
func(1)
Error: not found: value func
func(userVar)
That's the reason why macro shapeless.test.illTyped accepts a string rather than code block:
illTyped("""
val x: Int = "a"
""")
rather than
illTyped {
val x: Int = "a"
}
So the closest you can implement is
constructMacro("""
func(1)
func(userVar)
""")
def constructMacro(block: String): ParentWrapper = macro constructMacroImpl
def constructMacroImpl(c: blackbox.Context)(block: c.Tree): c.Tree = {
import c.universe._
val q"${blockStr: String}" = block
val block1 = c.parse(blockStr)
q"""construct(new Parent {
..$block1
})"""
}
You can annotate a variable
#constructMacro val x = {
func(1)
func(userVar)
}
// becomes
// val x: ParentWrapper = construct(new Parent {
// func(1)
// func(userVar)
// })
#compileTimeOnly("enable macro paradise to expand macro annotations")
class constructMacro extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro constructMacroImpl.impl
}
object constructMacroImpl {
def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
annottees match {
case q"$mods val $tname: $tpt = { ..$stats }" :: Nil =>
q"$mods val $tname: ParentWrapper = construct(new Parent { ..$stats })"
case _ =>
c.abort(c.enclosingPosition, "Not a val")
}
}
}
If I understood correctly, you just need to change object to def in the ObjectA block:
class User {
var userVar = 13
def makeParent = new Parent {
func(1)
func(userVar)
}
construct(makeParent)
}
and it'll do what you want.
Related
I am looking for a way to initialize a companion object with arguments. I tried this, it has the risk for re-instantiation.
private[mypackage] class A(in:Int) {
def method = {}
}
object A {
var singleton: Option[A] = None
def getInstance(): A = {
if(singleton.isDefined)
singleton.get
else {
throw InitializationException("Object not configured")
}
}
def getInstance(in:Int): A = {
singleton = Some(new A(in))
singleton.get
}
}
Is there a better way?
Pure Scala way
Scala allows you to create a singleton object of a type using object keyword. Scala ensures only one instance of A is available in the system.
private[myPackage] object A {
val configValue = Config.getInt("some_value")
def fn: Unit = ()
}
type of A object
scala> object A {}
defined object A
scala> :type A
A.type
more about singleton objects in scala Explanation of singleton objects in Scala
Guice Annotations
import com.google.inject.Singleton
#Singleton
class A (val value: Int) {
def fn: Unit = ()
}
Classic Java way
Use synchronized keyword to protect the getInstance from creating more than one object when called. of course constructor of the class has to be private
You can use a lazy val to delay creation of your singleton, and base it on a var that should be updated once during start-up sequence:
object A {
// will be instantiated once, on first call
lazy val singleton: A = create()
private var input: Option[Int] = None
// call this before first access to 'singleton':
def set(i: Int): Unit = { input = Some(i) }
private def create(): A = {
input.map(i => new A(i))
.getOrElse(throw new IllegalStateException("cannot access A before input is set"))
}
}
I have a sum type, Mapping:
sealed trait Mapping
final case class XMapping(a:String)
final case class FallbackMapping(mappings: List[Mapping])
I have a typeclass defined as follows:
final case class Param(x:String)
trait ParameterLoader[T] {
def load(mapping:T) : List[Param]
}
With some instances:
object DefaultParameterLoaders {
implicit val xParameterLoader= new QueryParameterLoader[XMapping] {
override def load(mapping: XMapping): List[Param] = List(Param(mapping.a))
}
implicit val fallbackParameterLoader = new ParameterLoader[FallbackMapping] {
override def load(mapping: FallbackMapping): List[Param] =
mapping.mappings.flatMap(x => ???)
}
}
I can't find a way to have the implicit instances passed to the flatMap above. The error I get is that I'm missing an instance of ParameterLoader[Mapping]. Is there some way of telling the compiler that it should use whatever typeclass instances are in scope?
The type system is looking for a ParameterLoader[Mapping] specifically, meaning that a ParameterLoader[XMapping]/ParameterLoader[FallbackMapping] is not specific enough. You need to provide a ParameterLoader[Mapping]. You can do this using your existing definitions.
implicit def mappingLoader(implicit xpLoader: ParameterLoader[XMapping], fmLoader: ParameterLoader[FallbackMapping]) = new ParameterLoader[Mapping] {
def load(mapping: Mapping): List[QueryParam] =
mapping match {
case xm: XMapping = xpLoader.load(xm)
case fm: FallbackMapping => fmLoader.load(fm)
}
}
Alternatively, have your flatmap perform the matching logic:
implicit def fallbackParameterLoader(implicit xpLoader: ParameterLoader[XMapping]) = new ParameterLoader[FallbackMapping] {
override def load(mapping: FallbackMapping): List[Param] =
mapping.mappings.flatMap {
case xm: XMapping = xpLoader.load(xm)
case fm: FallbackMapping => this.load(fm)
}
}
object GetTypeNameOfClassContainingSomeInstance {
implicit class GetsTypeNameOfContainingClass(x: Any) {
def containingClassTypeName: String = ???
}
}
class Foo {
import GetTypeNameOfClassContainingSomeInstance._
def foo(x: Any): Unit = {
println(s"${x.containingClassTypeName} owns this ${x.getClass}: $x")
// should print something like "Foo owns this <class of x>: x"
}
}
How can I make it so that containingClassTypeName knows well enough to return "Foo"?
def containingClassTypeName: String = {
val st = Thread.currentThread().getStackTrace
st(2).getClassName
}
This is an awful hack, but so is whatever you're trying to do.
i'm trying to import a implicit Write declaration from an embedded object into a function that produces a JSON object based on a set of case classes.
case class TestModel(test:String)
object TestModel {
def manyToJSON(models: List[TestModel]) = {
import writes.micro
Json.toJson(models)
}
object writes {
implicit val micro = Json.writes[TestModel]
}
}
unfortunately, the scala compiler complains:
No Json serializer found for type List[models.TestModel]. Try to implement an implicit Writes or Format for this type.
the fun part is, if i'm using the write object as a pure expression within the method, its working.
object TestModel {
def manyToJSON(models: List[TestModel]) = {
import writes.micro
writes.micro
Json.toJson(models)
}
object writes {
implicit val micro = Json.writes[TestModel]
}
}
how would i have to change my code to have the implicit in scope?
the reason case class implicit is not working is that it is just a definition not value. Use case object will solve this issue like object. Consider this code:
object MainClass {
def main(args: Array[String]) {
TestModel.manyToJSON(Nil)
}
}
case class TestModel(test:String)
object TestModel {
def manyToJSON(models: List[TestModel]) = {
import writes._
def print(implicit x: Int) = {
println(x)
}
print // print 3
}
object writes {
implicit val x: Int = 3
//implicit val x = 3 //compilation error
}
}
I am trying to write a generic method f[T](id:String) that is something like this:
case class A(x:String)
case class B(y:String)
case class C(z:String)
def f[T](id:String): T = { /* equivalent to T(id) */ }
val result1:A = f[A]("123") // returns A("123")
val result2:B = f{B]("345") // returns B("345")
val result3:C = f[C]("567") // returns C("567")
Unfortunately I cannot figure out how to work with the type T inside the method, besides using reflection. By "working with the type T" i mean for example being able to do something like the following, which I know doesn't work (for illustration purposes only):
T match {
case A => A(id)
case B => B(id)
}
or simply invoke T(ID) to create a new object of whatever type T is.
I can of course break up this into three methods:
def f1(id:String): A = { A(id) }
def f2(id:String): B = { B(id) }
def f3(id:String): C = { C(id) }
val result1:A = f1("123") // returns A("123")
val result2:B = f2("345") // returns B("345")
val result3:C = f3("567") // returns C("567")
but I'm hoping there is a way to keep it as one generic method to avoid some ugly boilerplate code duplication, and still be nearl as fast as the tree method version.
If you do not want to use reflection (ClassTag or TypeTag), you could use a Factory type class to achieve the desired functionality (unless it defeats the purpose of your generic function by generating a lot of duplicated simple code ;)).
case class A(s: String)
case class B(s: String)
case class C(s: String)
trait Factory[T] extends ((String) => T) {
def apply(arg: String): T
}
object Factory {
implicit object AFactory extends Factory[A] {
override def apply(arg: String): A = A(arg)
}
implicit object BFactory extends Factory[B] {
override def apply(arg: String): B = B(arg)
}
implicit object CFactory extends Factory[C] {
override def apply(arg: String): C = C(arg)
}
}
def create[T : Factory](arg: String): T = implicitly[Factory[T]].apply(arg)
create[A]("foo") | -> res0: A = A(foo)