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

Seb*_*ber 8 java macros scala scala-2.10

我不太了解这个领域.

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

Fai*_*aiz 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"
Run Code Online (Sandbox Code Playgroud)

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

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

  • 文本源预处理器(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))
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

足够近!但是这里的===方法用于模仿==哪些不能用于显而易见的原因,你不能将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)
Run Code Online (Sandbox Code Playgroud)

因此,这里使用宏来为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(())
Run Code Online (Sandbox Code Playgroud)

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

import assert
assert(2 + 2 == 4, "weird arithmetic")
Run Code Online (Sandbox Code Playgroud)

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

最后但并非最不重要的 - Scala 2.10宏将成为Scala的一部分 - 集成到标准发行版中 - 而不是由某些第三方库提供.