【问题标题】:Scala 2.10 macros compared to what is available in JavaScala 2.10 宏与 Java 中可用的宏相比
【发布时间】:2012-12-12 10:34:04
【问题描述】:

我不太了解这个领域。

与使用编译预处理器和 CGLIB、ASM、Byteman 等工具的 Java 中可能的情况相比,有人能解释一下 Scala 2.10 中使用宏的可能性吗?

【问题讨论】:

    标签: java scala macros scala-2.10


    【解决方案1】:

    [更新]:我尝试使用Slick 合并一个示例。很难为 Java(非 Scala)观众总结很多这些东西。

    Scala 2.10 中的宏作为一等公民,为该语言本身带来了成熟的元编程。

    // we often do this:
    log("(myList++otherList).size: " + (myList++otherList).size)
    // just to log the string:  
    // "(myList++otherList).size: 42"
    
    // Imagine log, if implemented as a macro:
    log((myList++otherList).size)
    // could potentially log both the EXPRESSION AND IT'S VALUE:
    // "(myList++otherList).size: 42"
    

    像这样的功能一般可以通过文本预处理或 以安全和干净的方式处理字节码?

    元编程在某个阶段归结为代码生成,并且没有限定,它是各种技术的总称——为了“不必自己编写一点代码”——如果有人要以 stage 的粗略顺序列举其中一些技术 - 从预编译的原始源代码到执行代码,列表可能如下所示:

    • 文本源预处理器 (C)
    • 模板系统 (C++) 要考虑的原语)
    • 反射在某些动态语言 (Ruby) 中“自然可用”
    • 静态类型语言 (Java) 中的运行时反射,它 以牺牲类型安全为代价为语言提供了一些活力
    • 字节码操作

    (请注意,我省略了在编译时运行的宏系统,下一段将详细介绍它们。)

    首先,考虑到上述所有技术本质上都是生成代码——无论是纯文本源代码的生成/操作,还是运行时字节码的生成/操作。

    宏系统呢?在编译时运行的宏不是在文本源代码或编译字节码上运行,而是在更有价值的阶段 - 它们在程序的AST上工作编译和那里可用的信息以及与编译过程的集成使它们具有一些强大的优势。它们可用于动态语言(如 Lisp 和 Dylan)和静态类型语言(Template HaskellScala 2.10 的自清洁宏)

    关于 Scala 2.10 中的宏,我想不到, 我想说最重要的优势是:

    类型安全:编译预处理器和字节码操作不能 利用类型系统。使用宏——尤其是 compile-time 宏——Scala 2.10 将拥有的那种宏,宏语言就是 Scala 本身,可以访问编译器的 API。任何类型的静态分析/检查源代码的完整类型信息通常可能仅在编译时才可用于宏。

    (安全)语法扩展:宏使调整语言结构以更好地实现 DSL 成为可能。一个很好的例子是Slick,它是一个数据库库,可让您将 SQL 查询表达为类型安全的 Scala 代码:

    首先考虑简单的 Scala 列表处理 - 还没有谈论数据库或宏:

    val coffees : List[Coffee] = // gotten from somewhere
    
    // get from this list a list of (name, price) pairs also filtering on some condition
    for {
      c <- coffees if c.supID == 101
      //                      ^ comparing an Int to an Int - normal stuff.
    } yield (c.name, c.price)
    
    // For Java programmers, the above is Scala's syntactic sugar for
    coffees.filter(c => c.supId == 101).map(c => (c.name, c.price))
    

    Slick,即使是 非宏 版本,也可以让您将数据库表视为 Scala 集合:这就是 非宏 版本(Slick 将其称为 提升嵌入 API)实现相同的方式,而是将 Coffees 改为 SQL 表:

    // WITHOUT MACROS (using enough of Scala's other features):
    // Generates a query "SELECT NAME,PRICE FROM COFFEES IF SUP_ID = 101" 
    for {
      c <- Coffees if c.supID === 101
      //                      ^ comparing Rep[Int] to Rep[Int]!
      // The above is Scala-shorthand for
      //     c <- Coffees if c.supID.===(Const[Int](101))
    } yield (c.name, c.price)
    

    够近了!但是这里使用=== 方法来模仿==,原因很明显,您无法将SQL 列的表示 与实际的Int 进行比较。

    如果您手头有 Scala 2.10,这在宏版本中已解决:

    // WITH MACROS:
    // Generates a query "SELECT NAME,PRICE FROM COFFEES IF SUP_ID = 101" 
    for {
      c <- coffees if c.supID == 101
      //                      ^ comparing Int to Int!
    } yield (c.name, c.price)
    

    因此,这里利用宏来为 SQL 以及普通的 Scala 集合提供相同的语法。除了 Scala 中已经存在的表现力和组合性之外,类型安全和宏卫生的结合使宏具有吸引力。

    另外,请从另一个答案提供的链接中考虑这个示例:

    def assert(cond: Boolean, msg: Any) = macro impl
    
    def impl(c: Context)(cond: c.Expr[Boolean], msg: c.Expr[Any]) =
      if (assertionsEnabled)
        // if (!cond) raise(msg)
        If(Select(cond.tree, newTermName("$unary_bang")),
            Apply(Ident(newTermName("raise")), List(msg.tree)),
            Literal(Constant(())))
      else
        Literal(Constant(())
    

    因此定义了一个宏assert,其用法类似于方法调用:

    import assert
    assert(2 + 2 == 4, "weird arithmetic")
    

    只是,因为assert 是一个宏而不是一个方法,布尔表达式2 + 2 == 4 只有在启用断言时才会被计算请注意,有一个简写可以帮助表达 AST,但希望这个例子这样更清楚

    最后但同样重要的是 - Scala 2.10 宏将成为 Scala 的一部分 - 集成到标准发行版中 - 与某些第三方库提供的相反。

    【讨论】:

    • 感谢您的回答。我正在使用 MyBatis,并且有一个 maven 插件代码生成器,它为我们所有的表创建类型安全 dao 的配置(具有最常见的用例)。与使用代码生成器相比,Slick 有什么优势?
    • 使用类型宏......你不需要一个。输入到数据库的连接字符串,编译器会直接从那里获取它需要的所有类型。如果数据库发生变化,IDE 会在几微秒后开始对你大喊大叫,因为没有中间步骤。
    • 所以你的意思是主要优势是快速失败的行为
    • @SebastienLorber 感谢您提供有关 MyBatis 的信息 - 您有链接(关于类型安全的 maven 插件)吗?我对 Java ORM 有点脱节,我将这完全归咎于 Scala(和 Slick)的美好生活。
    • @SebastienLorber - Slick 的一个优点是,由于它允许您将查询表达为普通的 scala,查询是 可组合的 - 您可以从单个查询构建复杂的查询静态类型检查。这太好了,但你必须先喝下 Scala 凉水……
    【解决方案2】:

    除了 Faiz 提到的几点,Scala 宏是hygienic:它们不会因意外捕获标识符而受到影响。特别是,Scala 宏是self cleaning:通过具体化来实现卫生,而具体化本身就是一个宏。如需更深入地了解其工作原理,请参阅Scala Macros, a Technical Report

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-25
      • 2012-11-30
      • 2013-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多