【发布时间】:2012-12-12 10:34:04
【问题描述】:
我不太了解这个领域。
与使用编译预处理器和 CGLIB、ASM、Byteman 等工具的 Java 中可能的情况相比,有人能解释一下 Scala 2.10 中使用宏的可能性吗?
【问题讨论】:
标签: java scala macros scala-2.10
我不太了解这个领域。
与使用编译预处理器和 CGLIB、ASM、Byteman 等工具的 Java 中可能的情况相比,有人能解释一下 Scala 2.10 中使用宏的可能性吗?
【问题讨论】:
标签: java scala macros scala-2.10
[更新]:我尝试使用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 的粗略顺序列举其中一些技术 - 从预编译的原始源代码到执行代码,列表可能如下所示:
(请注意,我省略了在编译时运行的宏系统,下一段将详细介绍它们。)
首先,考虑到上述所有技术本质上都是生成代码——无论是纯文本源代码的生成/操作,还是运行时字节码的生成/操作。
宏系统呢?在编译时运行的宏不是在文本源代码或编译字节码上运行,而是在更有价值的阶段 - 它们在程序的AST上工作编译和那里可用的信息以及与编译过程的集成使它们具有一些强大的优势。它们可用于动态语言(如 Lisp 和 Dylan)和静态类型语言(Template Haskell 和 Scala 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 的一部分 - 集成到标准发行版中 - 与某些第三方库提供的相反。
【讨论】:
除了 Faiz 提到的几点,Scala 宏是hygienic:它们不会因意外捕获标识符而受到影响。特别是,Scala 宏是self cleaning:通过具体化来实现卫生,而具体化本身就是一个宏。如需更深入地了解其工作原理,请参阅Scala Macros, a Technical Report。
【讨论】: