8

我不太了解这个领域。

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

4

2 回答 2

16

[更新]:我尝试使用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"

像这样的功能通常可以通过文本预处理或字节码操作以安全和干净的方式实现吗?

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

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

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

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

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

关于 Scala 2.10 中的宏,我认为最重要的优势是:

类型安全:编译预处理器和字节码操作不能利用类型系统。使用宏——尤其是编译时宏——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)实现相同Coffee的方式,而是作为 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 的一部分 -集成到标准发行版 中 - 而不是由某些第三方库提供。

于 2012-12-12T10:47:51.433 回答
4

除了 Faiz 提到的几点之外,Scala 宏是卫生的:它们不会因意外捕获标识符而受到影响。特别是,Scala 宏是自我清洁的:通过具体化来实现卫生,而具体化本身就是一个宏。要更深入地了解其工作原理,请参阅技术报告 Scala 宏

于 2012-12-12T11:39:21.433 回答