Scala 2.10宏与Java中可用的宏相比

时间:2012-12-12 10:34:05

标签: java scala macros scala-2.10

我不太了解这个领域。

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

2 个答案:

答案 0 :(得分: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"

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

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

  • 文本源预处理器(C)
  • 模板系统(C ++)(许多人可能会争辩说上面也是如此) 原始要考虑)
  • 在一些动态语言中自然可用的反射(Ruby)
  • 静态类型语言(Java)中的运行时反射,其中 以类型安全为代价给语言带来一些活力
  • 字节码操作

(注意我省略了在编译时运行的宏系统,在下一段中更多关于它们。)

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

宏观系统怎么样?在编译时运行的宏和不在文本源代码上运行,也不在编译的字节代码上运行,但是在更有价值的阶段运行 - 它们在程序的 AST 上运行编译和那里可用的信息以及与编译过程的集成使它们具有一些强大的优势。它们以动态语言(如Lisp和Dylan)和静态类型语言( Template Haskell Scala 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列的表示与实际{{1}进行比较的明显原因而无法使用Int方法1}}。

这是在宏版本中解决的,如果你有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表现力和组合性,使Macros更具吸引力。

另外,请从其他答案提供的链接中考虑此示例:

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的一部分 - 集成到标准发行版中 - 而不是某些第三方图书馆提供的。

答案 1 :(得分:4)

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