【问题标题】:Is it possible to write a scala macro whose returntype depends on argument?是否可以编写一个返回类型取决于参数的 scala 宏?
【发布时间】:2012-08-18 11:21:44
【问题描述】:

对于 DSL,我希望能够执行以下操作:

object Creator { 
   def create[T](s :String) :Foo[T] = macro createImpl[T]
   def createImpl[T](c :Context)(s :c.Expr[String]) : c.Expr[Foo[T]] = {
        reify(new Foo[Any]())
   }
 }

我的问题是将 reify 中的 Any 替换为可以返回正确参数化版本的内容。

(上面我使用字符串参数,但在最终版本中我计划使用类 T 的伴随对象作为标记来了解 Function1[T,Unit] 的参数类型)

【问题讨论】:

    标签: macros scala-2.10 scala-macros


    【解决方案1】:

    您需要: a)写new Foo[T]()而不是new Foo[Any]()(简单) b) 在宏中传递T 类型的表示,即AbsTypeTag[T] 类型的值,通过使用上下文绑定声明参数T[T: c.AbsTypeTag]

    这是我在 Scala 2.10.0-M7 中测试的代码。 编辑。在 2.10.0-RC1 中,AbsTypeTag 已重命名为 WeakTypeTag。其他关于宏和类型标签的内容保持不变。

    Creator.scala:

    import language.experimental.macros
    import reflect.macros.Context
    class Foo[T]
    object Creator {
      def create[T](s: String): Foo[T] = macro createImpl[T]
      def createImpl[T: c.AbsTypeTag](c: Context)(s: c.Expr[String]): c.Expr[Foo[T]] = {
        import c.universe._
        reify(new Foo[T]())
      }
    }
    

    MacroClient.scala:

    object Main extends App {
      println (Creator.create[Int](""))
    }
    

    注意,如果省略类型参数,你会得到一个奇怪的错误:

    scala> Creator.create[Int]("")
    res2: Foo[Int] = Foo@4888884e
    
    scala> Creator.create("")
    <console>:12: error: macro has not been expanded
                  Creator.create("")
                          ^
    

    你也写:

    (上面我使用字符串参数,但在最终版本中我打算使用 类 T 的伴生对象作为知道参数类型的标记 Function1[T,Unit])

    但如果我做对了,这听起来是个坏主意。调用语法不是写Creator.create[T](otherArgs),而是类似于Creator.create(T, otherArgs),这不是一个很大的优势(如果有的话)。但是你甚至不能得到后一种语法:如果class Aobject A 是同伴,它们的类型不相关:第一个类型为A,第二个类型为A.type,其中A 是同伴对象而不是 A 类的类型。


    更新:如果您可以控制Foo,如何让Creator create Foo 语法工作并返回Foo 的实例。 由于您询问reifyAny 类型参数,我假设您询问的是类型参数。仅当您希望 Creator.createstatic 返回类型为 T 而不是 Any 时才有意义;否则,您应该澄清您的问题。

    这里的问题与宏无关。 Creator create Foo 将对象Foo 传递给Creator.create,其声明需要通过类型表达式表达Foo.type,类型Foo。 Scala 中的类型表达式非常有限——例如​​,它们不能使用反射。但是给定一个类型,他们可以选择它的类型成员。

    trait Companion[Class]
    //How to declare a companion
    class Foo
    object Foo extends Companion[Foo]
    /*I'm cheating: an implementation of Companion does not need to be a true Companion. You can add documentation to explain how Companion is supposed to be used. */
    object Bar extends Companion[Foo]
    //But this is also useful - you can't create your own companion objects for pre-existing types, but you can still create the right instances of Companion:
    object pInt extends Companion[Int]
    object Creator {
      //T with Companion[S] is needed to workaround a type inference bug (SI-5298) and allow S to be correctly inferred.
      def create[S, T <: Companion[S]](obj: T with Companion[S]): S = ???
    }
    

    这是有限的,因为您需要更改伴随对象,但我很确定您不能做得更好。我不知道,在类型表达式中(在声明 create 的返回类型时可以使用什么来代替 S)从伴随对象获取到其关联的类类型,而且我认为没有。

    现在,将上面的内容更改为使用宏很简单:

    import language.experimental.macros
    import reflect.macros.Context
    class Foo[T]
    object Creator {
      //T with Companion[S] is needed to workaround a type inference bug (SI-5298) and allow S to be correctly inferred.
      def create[S, T <: Companion[S]](obj: T with Companion[S]): Foo[S] = macro createImpl[S, T]
      def createImpl[S: c.AbsTypeTag, T <: Companion[S]: c.AbsTypeTag](c: Context)(obj: c.Expr[T with Companion[S]]): c.Expr[Foo[S]] = {
        import c.universe._
        reify(new Foo[S]())
      }
    }
    

    【讨论】:

    • 1.) 我要的是参数而不是类型参数... 2.) Creator create "Foo" 或者更确切地说是Creator create Foo 确实有一些优势...(从 DSL 的角度来看)。 3.) 类和它们的同伴是相关的,并且在反射 api 中有解决它们的函数。
    • 我不明白你的意思 1)。剩下的,我很确定没有通用的方法来获得Creator create Foo 语法——而不是任意的Foo。不能通过类型推断来利用类与其伙伴之间的关系。我将单独发布一个非通用解决方案。
    • 最后我把非通用的解决方案加到了上述帖子的最后。
    • 感谢您的更新...同时我以不同的方式实现了它。基本上手动创建树而不是使用 reify。我认为您的答案是尽可能地使用 reify。 ...我会写一个答案来描述我在接下来的几天里做了什么。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-18
    • 2022-01-09
    • 2021-11-04
    • 2016-02-27
    • 2020-11-20
    相关资源
    最近更新 更多