Scala: макрос для создания экземпляра из тела класса - PullRequest
0 голосов
/ 24 июня 2019

Я создаю DSL в Scala, и для этого мне нужно хранить «экземпляры» класса (в данном случае Parent), за исключением того, что эти «экземпляры» необходимо создавать несколько раз во время выполнения.Поэтому вместо этого я храню «функции конструктора» - лямбду, которая создает экземпляр.

рассмотрим следующий код - представьте, что userVar может измениться во время выполнения, и обновленное значение должно использоваться при построении экземпляров.

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)
  }
}

Более естественным способом выражения того, что я хочу, было бы следующее (используя приведенные выше определения):

class User {
  var userVar = 13
  object ObjectA extends Parent {
    func(1)
    func(userVar)
  }

  construct(ObjectA)
}

Однако это кажется невозможным, учитывая, что ObjectA создается немедленнои не имеет «конструктора».

Я думаю, что при некотором творческом использовании макросов я мог бы вместо этого сделать следующее:

class User {
  var userVar = 13

  constructMacro {
    func(1)
    func(userVar}
  }
}

и иметь преобразование constructMacroкод construct(new Parent {code block goes here}).

Как бы я это сделал?

Или есть лучший способ избежать неловкого вызова construct(new Parent{...})?Мое требование заключается в том, что где-то в классе User хранится ссылка, которую я могу неоднократно вызывать и получать новые экземпляры определения Parent, которые отражают новые значения, используемые в их построении, и в идеале вызов construct должен возвращатьобъект-оболочка для этой ссылки.

Ответы [ 2 ]

1 голос
/ 25 июня 2019

К сожалению, макросы не помогут.

Макро-аннотации (которые расширяются перед проверкой типа) не может комментировать блоки кода :

@constructMacro {
  func(1)
  func(userVar)
}

незаконно.

Макросы Def (которые раскрываются при проверке типов) проверяется тип аргументов перед развертыванием макросов . Итак

constructMacro {
  func(1)
  func(userVar)
}

не компилируется:

Error: not found: value func
      func(1)
Error: not found: value func
      func(userVar)

Именно поэтому макрос shapeless.test.illTyped принимает строку, а не блок кода:

illTyped("""
  val x: Int = "a"
""")

вместо

illTyped {
  val x: Int = "a"
}

Итак, самое близкое, что вы можете реализовать -

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
  })"""
}

Вы можете аннотировать переменную

@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")
    }
  }
}
0 голосов
/ 24 июня 2019

Если я правильно понял, вам просто нужно изменить object на def в блоке ObjectA:

class User {
  var userVar = 13
  def makeParent = new Parent {
    func(1)
    func(userVar)
  }

  construct(makeParent)
}

и он будет делать то, что вы хотите.

...