Scala元编程:伊甸园初窥

栏目: Scala · 发布时间: 5年前

内容简介:本文的行文风格不求阅读意义上的可读性,而是期望读者能够跟着本文的一些探索,自己做一些尝试,即git clone本文涉及的代码阅读并实践。至于Scala元编程的一些介绍,请阅读 @王在祥 的《神奇的Scala Macro之旅系列》:一,我们从

本文的行文风格不求阅读意义上的可读性,而是期望读者能够跟着本文的一些探索,自己做一些尝试,即git clone本文涉及的代码阅读并实践。

至于Scala元编程的一些介绍,请阅读 @王在祥 的《神奇的Scala Macro之旅系列》:一, , ,

绕不开的Sbt

我们从 Macro Paradise的例子 开始。有点遗憾的是,这个例子仍然在使用旧的Sbt。所以,我们的第一步是把构建的定义升级到当前Sbt的最新版。完整的项目见我fork的 sbt-example-paradise

首先,在project/build.properties中指定:

sbt.version=1.2.7
复制代码

然后,再修改build.sbt为:

val paradiseVersion = "2.1.0"

lazy val commonSettings = Seq(
  scalaVersion := "2.12.8",
  addCompilerPlugin("org.scalamacros" % "paradise" % paradiseVersion cross CrossVersion.full)
)

lazy val root = (project in file("."))
  .aggregate(core, macros)

lazy val macros = (project in file("macros"))
  .settings(commonSettings)
  .settings(
    libraryDependencies ++= Seq(
      "org.scala-lang" % "scala-reflect" % scalaVersion.value
    )
  )

lazy val core = (project in file("core"))
  .settings(commonSettings)
  .dependsOn(macros)
复制代码

我们可以对比一下这一段SBT项目构建的定义和Maven的构建定义:

  1. commonSettings相当于Maven里面最顶层的pom.xml
  2. root相当于Maven中指定子模块的那几行
  3. macros和core分别对应两个子目录,其中,core依赖macros。

最后,运行一下这个例子:

$ sbt
> project core # 切换到core子项目
> compile
> run
复制代码

输出结果如下:

hello
复制代码

你好

@hello
object Test extends App {
  println(this.hello)
}
复制代码

我们实际上运行的这段代码异常简洁。this指代Test这个独立对象(stand-alone object)本身,调用了一个不存在的方法hello。我们的问题是,@hello施放了什么样的魔法,生成了这样一个不存在的hello方法。

忽略别的语法细节,我们只看下面的这段代码:

annottees.map(_.tree).toList match {
        case q"object $name extends ..$parents { ..$body }" :: Nil =>
          q"""
            object $name extends ..$parents {
              def hello: ${typeOf[String]} = "hello"
              ..$body
            }
          """
}
复制代码

从直观的感受,我们能猜想到, $name 即Test, $parents 即App, $body 就是代码的主体。parents和body前面有两个点,区别于name。

通过 $name$parents$body 这种特殊的语法形式,我们实际上把:

object Test extends App {
  println(this.hello)
}
复制代码

变换成了:

object Test extends App {
  println(this.hello)

  def hello: String = "hello"
}
复制代码

尽管我们或许不知道其中的语法所对应的语义,更不清楚具体的实现机制,但这部分代码的可读性是非常棒(intuitive)的。

下一步?

现在大概知道了 @hello 所施放的黑魔法。下一步,我们就得弄明白这个简单的例子中,每一行代码的含义。

否则,任何拙劣的模仿和尝试,都是在浪费时间。

那我们应该如何学习这些黑魔法呢?官网的文档可读性并不好,而且不少是过时的。网络上也没有特别友好的面向新人的教程。

追本溯源,前面的项目实际上涉及到两个子项目,scala-reflect和paradise。在scala的源代码中,scala-reflect相关的代码单元测试并不多,所以我们从paradise的单元测试开始阅读。

git clone git@github.com:scalamacros/paradise.git
复制代码

可以将sbt的版本统一到1.2.7。这样做,主要为了防止去下载另外一个Sbt的版本,浪费大量时间。

很幸运,更改版本之后,项目可以正常编译,测试。

$ sbt
> compile
> project tests
> test
复制代码

这个sbt的终端保持开启,然后用Intelli Idea打开整个项目,这样,应该能够更快地打开整个项目,我们在Sbt的会话中可以看到无端跳出来的日志:

[info] new client connected: network-1
复制代码

大致浏览一下这些单元测试的代码,可以获得一些初步的印象。

Macro Paradise将在Scala 2.13.x中内置

另外,这个 paradise插件 将在Scala 2.13.x中内置,所以我们还需要看一下Scala 2.13.x分支的代码。通过 git grep paradise ,可以看到一些蛛丝马迹。paradise的源代码主要被引入到了compiler和reflect下面,而单元测试则是在tests/macro-annot下面。

此时,我们可以将前面的sbt-example-paradise升级到Scala 2.13.x:

val paradiseVersion = "2.1.0"

lazy val commonSettings = Seq(
  scalaVersion := "2.13.0-M5",
  scalacOptions ++= Seq("-Ymacro-annotations")
)

lazy val root = (project in file("."))
  .aggregate(core, macros)

lazy val macros = (project in file("macros"))
  .settings(commonSettings)
  .settings(
    libraryDependencies ++= Seq(
      "org.scala-lang" % "scala-reflect" % scalaVersion.value
    )
  )

lazy val core = (project in file("core"))
  .settings(commonSettings)
  .dependsOn(macros)
复制代码

为了避免构建定义太复杂,我们直接新开一个 分支

这里请注意一下编译选项 scalacOptions ++= Seq("-Ymacro-annotations") 。我是在Scala源代码中通过 git grep paradise 瞥见的这个编译选项,然后简单看了一下相关代码,了解到了其中的作用。这边第二次提及 git grep ,是因为在日常工作中,发现一些小伙伴不知道有 git grep 这么好用的工具,觉得十分诧异。

不过细想也很正常,很多时候,我们自己所认为的Common Sense,别人极有可能根本不了解。

所以,我们直接研究最新的2.13.x,不需要任何依赖,就可以探索Scala的元编程。

小结

本文从一个Macro Paradise项目的示例项目,从构建和代码阅读的细节入手,从大体上去感知Macro Paradise的某个具体的应用场景。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

失业的程序员

失业的程序员

沈逸 / 2014-5-1 / 39.00元

这是一个程序员从失业到自行创业的奋斗历程,虽然囧事连连、过程曲折,却充满了趣味。本书以作者的真实创业经历为主线,文字幽默诙谐,情节生动真实,包括了招聘、团队管理和用户公关,以及技术架构设计、核心代码编写、商务谈判、项目运作等场景经验。 从初期的创业伙伴、领路人,到商业竞争对手,各种复杂的关系在各个关键时刻却都发生了意想不到的逆转。在历经千辛万苦,眼看快要成功时,主人公却几乎再次失业。 ......一起来看看 《失业的程序员》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

MD5 加密
MD5 加密

MD5 加密工具

html转js在线工具
html转js在线工具

html转js在线工具