Scala 宏介绍

Posted ScalaCoder

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala 宏介绍相关的知识,希望对你有一定的参考价值。

在看宏之前,先简单总结一下特殊的泛型使用。


Scala 泛型

一般是定义类型的时候使用
  • 上界 <:

  • 下界 >:

  • 视界 <%  对于[A <% T]必须使用隐式转换将有界类型A转换为有界类型T

  • 边界 :

  • 协变 +T

  • 逆变 -T

演变的其他限定

  • <:<

  • =:=

  • <%<

对于A和B这两种类型,如果编译器可以找到A <:< B类型的隐式值,则它知道A符合B(是B的子类型)。同样,如果编译器可以找到A =:= B类型的隐式值,那么它知道A和B的类型相同。它们在编码通用类型约束时很有用。

下面一些很好的问题和回答,有空可以看看。

1. What's the difference between A <: B and +B in Scala?(https://stackoverflow.com/questions/4531455/whats-the-difference-between-ab-and-b-in-scala)
2. <:<、=:=、<%<(https://www.scala-lang.org/old/node/10632)
3. ProgrammingInScala.pdf,从第61页开始(https://cs.uwaterloo.ca/~brecht/courses/702/Possible-Readings/scala/ProgrammingInScala.pdf)
4. What are Scala context and view bounds?(https://stackoverflow.com/questions/4465948/what-are-scala-context-and-view-bounds)

示例如下

scala> def prove[T](implicit proof: T) = true
prove: [T](implicit proof: T)Boolean

scala> prove[Null <:< String]
res0: Boolean = true

scala> prove[String <:< AnyRef]
res1: Boolean = true

scala> prove[Null <:< Long]
<console>:9: error: Cannot prove that Null <:< Long.
prove[Null <:< Long]
^

scala> prove[Long <:< AnyRef]
<console>:9: error: Cannot prove that Long <:< AnyRef.
prove[Long <:< AnyRef]
^

scala> prove[AnyRef =:= java.lang.Object]
res4: Boolean = true

scala> trait Foo { type T }
defined trait Foo

scala> val foo = new Foo { type T = Int }
foo: java.lang.Object with Foo{type T = Int} = $anon$1@258361f6

scala> prove[foo.T =:= Int]
res5: Boolean = true

scala> prove[Foo#T <:< AnyRef]
<console>:10: error: Cannot prove that Foo#T <:< AnyRef.
prove[Foo#T <:< AnyRef]
^

scala> prove[Foo#T <:< Any]
res9: Boolean = true

关于宏

  • 宏是程序源代码在编译前完成的对源代码一系列操作的定义

  • 宏一般用来动态生成要编译的代码

  • C 中的宏

    • C 中的宏比较简单

#include <stdio.h>
#define PI 3.141592653589# FLAG 1
defineint main() {
#ifdef FLAG
double v = 3 * PI * 2;
printf("%f\n",v);
#else
double v = 3 * PI * 2;
printf("%d\n",v);
#endif}

Scala 宏

  1. Scala 中的宏要复杂的多,但它对生成代码的方式,提供了更大的灵活性。

  2. Scala 宏仍然是用 Scala 写的,一定程序上保证了开发体验的一致。

  3. Scala 宏总是要返回一个AST,这需要你对 Scala AST 有一定的了解。

  4. 但 Quasiquotes(准引用) 可以帮你轻松生成 AST。

准引用是一种简洁的表示法,使您可以轻松地操作Scala AST 语法树。

关于 Context

  • Context 封装了一个编译时Universe (scala.reflect.macros.Universe)

  • Context 同时也有一个 macroApplication,它提供了一个对宏展开处完整的AST

  • 类型检查,编译警告、报错等

黑盒和白盒

一般来讲,黑盒(blackbox.Context)会比白盒(whitebox.Context)有更严格的类型检查

黑盒例子

黑盒宏的使用,会有四点限制,主要方面是

  • 类型检查

  • 类型推导

  • 隐式推导

  • 模式匹配

具体看官网 blackbox-whitebox(https://docs.scala-lang.org/overviews/macros/blackbox-whitebox.html

import scala.reflect.macros.blackbox

object Macros {
def hello: Unit = macro helloImpl

def helloImpl(c: blackbox.Context): c.Expr[Unit] = {
import c.universe._
c.Expr {
Apply(
Ident(TermName("println")),
List(Literal(Constant("hello!")))
)
}
}
}

白盒例子

import scala.reflect.macros.blackbox

object Macros {
def hello: Unit = macro helloImpl

def helloImpl(c: blackbox.Context): c.Tree = {
import c.universe._
//可见写起来黑盒也更方便
c.Expr(q"""println("hello!")""")
}
}

了解了Macros的两种规范之后,我们再来看看它的两种用法,一种和C的风格很像,只是在编译期将宏展开,减少了方法调用消耗。还有一种用法,我想大家更熟悉,就是注解,将一个宏注解标记在一个类、方法或者成员上,就可以将所见的代码,通过AST变成everything。

定义一个宏

如果我们要传递一个参数或者泛型呢?

object Macros {
def hello2[T](s: String): Unit = macro hello2Impl[T]

def hello2Impl[T](c: blackbox.Context)(s: c.Expr[String])(tag: c.WeakTypeTag[T]): c.Expr[Unit] = {
import c.universe._
c.Expr {
Apply(
Ident(TermName("println")),
List(
Apply(
Select(
Apply(
Select(
Literal(Constant("hello ")),
TermName("$plus")
),
List(
s.tree
)
),
TermName("$plus")
),
List(
Literal(Constant("!"))
)
)
)
)
}
}
}

和之前的不同之处,暴露的方法hello2主要在于多了参数s和泛型T,而hello2Impl实现也多了两个括号

  • (s: c.Expr[String])

  • (tag: c.WeakTypeTag[T])

c.Expr

这是Macros的表达式包装器,里面放置着类型String,为什么不能直接传String呢?当然是不可以了,因为宏的入参只接受Expr,调用宏传入的参数也会默认转为Expr。这里要注意, 这个(s: c.Expr[String])的入参名必须等于hello2[T](s: String)的入参名。

WeakTypeTag[T]

有时我们无法为泛型提供边界,就需要使用WeakTypeTag,此时无法使用TypeTag和ClassTag。但是应尽可能的提供更加具体的类型给WeakTypeTag。

像Manifests一样,TypeTags总是由编译器生成,并且可以通过三种方式获得,typeTag、classTag或weakTypeTag。

manifests(https://docs.scala-lang.org/overviews/reflection/typetags-manifests.html)

关于 Universe

  • scala.reflect.api.Universe 提供了一个用来 Scala 反射的完整的操作集合,比如查看类型的成员,或反射出子类型

    • scala.reflect.api.JavaUniverse 是一个用在 JVM 实例上的对 Scala 反射的一个实现

    • scala.reflect.macros.Universe 是在进行 Scala 宏编程时对 Scala 反射的实现

  • Universe 可以看作 Scala 反射的一个入口,主要混合了以下一些类型

    • Types 类型相关

    • Symbols 定义相关

    • Trees 抽象语法树相关

    • Names term 和 type names 相关

    • Annotations 注解相关

    • Positions 源码位置相关

    • FlagSet represent sets of flags that apply to symbols and definition trees

    • Constants 编译时常量相关

想要了解 Universe 最好是应用Scala反射接口,如使用Scala注解。

关于 Scala AST

编译器在编译代码前首先会把 source code 解析为编译器更容易消化的抽象语法树 (Abstract Syntax Tree AST)。

AST在线测试(https://astexplorer.net/)

以上是关于Scala 宏介绍的主要内容,如果未能解决你的问题,请参考以下文章

一篇入门 — Scala 宏

使用Scala宏无法解释的类型不匹配

如何使用构建器模式通过 Scala 宏返回内部对象

inline内联函数

记录 Scala 2.10 宏 [关闭]

Lisp宏和scala宏有什么区别?