Scala入门之工具篇

我初次接触Scala时,由于对Scala相关的工具不够熟悉,学习的效率低下。所以本文主要介绍Scala编程所必备的工具。一般而言,我们接触一门编程语言,都需要接触这门语言的编译器、REPL、构建工具、集成开发环境、文档(当然,因语言而异,编译器和REPL并不是每个语言都有的)。

REPL

行编辑

Ctrl+F 向前移动光标
Ctrl+B 向后移动光标
Ctrl+N 命令历史中的下一个
Ctrl+P 命令历史中的上一个
Ctrl+R 搜索命令历史
Tab 自动补全

命令

REPL中的命令以:开始,比如:help用于显示所有命令,:quit用于退出。

其中模式相关的命令有:

  1. :paste,多行模式,用于粘贴多行代码
  2. :silent,安静模式,用于打开/关闭结果的输出
  3. :power,高级用户模式,TODO

REPL历史相关的有:

  1. history [num],显示num数目的命令历史
  2. h?,搜索命令历史
  3. :save <path>,保存逐行的代码到指定文件
  4. :load <path>, 从文件中逐行加载代码
  5. :imports,TODO

语言相关:

  1. type [-v] <expr>,显示一个表达式的类型而无需求值,v表示verbose
  2. kind [-v] <expr>,显示一个表达式类型(type)的类别(kind)

拨开糖衣

Scala的语言中有许多语法糖,在REPL中可以用ToolBox解析字符串:

scala> import scala.tools.reflect.ToolBox
scala> val tb = scala.reflect.runtime.currentMirror.mkToolBox()
scala> tb.parse("for(x <- 1 to 3) println(x)")
res0: tb.u.Tree = 1.to(3).foreach(((x) => println(x)))

对于解析出来的语法树,还可以eval:

scala> val tree = tb.parse("for(x <- 1 to 3) println(x)")
tree: tb.u.Tree = 1.to(3).foreach(((x) => println(x)))
scala> tb.eval(tree)
1
2
3
res1: Any = ()

还可以使用universe.reify方法:

scala> import reflect.runtime.universe._
import reflect.runtime.universe._

scala> reify(for(x <- 1 to 3) println(x))
res2: reflect.runtime.universe.Expr[Unit] = Expr[Unit](Predef.intWrapper(1).to(3).foreach(((x) => Predef.println(x))))

这个方法就功能上和ToolBox的区别在于,使用它来定位隐式转换比较方便。

除了这两种方法之外,还可以使用scala -Xprint:typer:

sadhen@debian:~/.sbt$ scala -Xprint:typer -e 'for(x <- 1 to 3) println(x)'
[[syntax trees at end of                     typer]] // scalacmd9042395249085550738.scala
package <empty> {
  object Main extends scala.AnyRef {
    def <init>(): Main.type = {
      Main.super.<init>();
      ()
    };
    def main(args: Array[String]): Unit = {
      final class $anon extends scala.AnyRef {
        def <init>(): <$anon: AnyRef> = {
          $anon.super.<init>();
          ()
        };
        scala.this.Predef.intWrapper(1).to(3).foreach[Unit](((x: Int) => scala.this.Predef.println(x)))
      };
      {
        new $anon();
        ()
      }
    }
  }
}

1
2
3

Scala的启动

可以这样设置启动脚本:

alias scala='scala -deprecation -feature -i ~/.scalarc'

在脚本中可以把上文中提到的ToolBox和reify工具准备好,还可以做一些简单的定制,比如scala的诊断方法(3) 在repl下统计方法的执行时间

构建工具

通常,我们用SBT来构建Scala项目,但并不总是如此,有时候为了工程上的统一,也使用Maven。使用SBT的坑点在于在天朝这种特殊的网络环境下,软件包的下载尤为缓慢,甚至无法下载。解决方案之一就是科学上网,之二便是使用Repox社区公服

只要在$HOME/.sbt/下新建一个文件repositories,其中内容如下,就可以使用Repox的社区公服。

[repositories]
  local
  repox-maven: http://repox.gtan.com:8078/
  repox-ivy: http://repox.gtan.com:8078/, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]
  community-plugins-ivy-releases: https://dl.bintray.com/sbt/sbt-plugin-releases/, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]

你可以用sbt -debug检查以上配置是否生效,如果没有,则需要使用sbt -Dsbt.override.build.repos=true。方便起见,可以在.bashrc中设置别名,也可以在sbt的配置文件sbtopts中配置。

在使用Intellij Idea的时候,如果发现以上配置没有生效,则需要在Intellij Idea的sbt配置面板中配置。具体的配置位置参考这里

Tips1: 有时候,你用了sbt -debug但是sbt仍然卡在某个地方,你却不知道发生了什么。这个时候,你可以在project/plugins.sbt加入这样一行:logLevel := Level.Debug。你就可以看到更加详细的日志了。

Tips2: 在使用配置好的repositories下载Scala.js相关的依赖时,有时候并不能成功下载。此时可以尝试一下重命名repositories,使用sbt默认的配置下载依赖。

集成开发环境

我使用Intellj Idea,只需在其中安装Scala插件,就可以愉快地开发Scala项目了。在开发过程中,我们可以当前的项目的任一目录上右击,在New中选择新建一个Scala Worksheet。在Scala Worksheet中可以使用交互模式,每次只要你写一行代码,Worksheet都会为你实时求值,并显示在右侧。这种模式结合了REPL的优点,使得你可以了解到代码的每一“行”所求得的结果,非常有利于新手学习Scala。

References

  1. 使用SBT构建Scala应用
  2. scala的诊断方法(1) 使用-Xprint:typer看语法糖的背后
  3. scala的诊断方法(2) 在repl下用reify查看表达式的翻译结果
  4. REPL下的几种模式
  5. SBT免翻墙手册