Scala教程– SBT,scalabha,软件包,构建系统

前言

这是面向初学者的Scala教程的第11部分。 该博客上还有其他文章,您可以在我正在为其创建的计算语言学课程的链接页面上找到这些链接和其他资源。 另外,您可以在“ JCG Java 教程”页面上找到本教程和其他教程系列。

本教程介绍了如何使用SBT (简单构建工具)构建Scala应用程序。 这将在Scalabha软件包的背景下完成,该软件包是我主要为我的《计算语言学入门》课创建的 。 Scalabha中提供了一些支持代码,用于一些基本的自然语言处理任务。 目前最相关的是Scalabha中支持该类词性标记作业代码

上一教程介绍了如何使用scalac编译Scala代码,然后使用scala运行它。 我们最终遇到的一个问题是,有一些生成的类文件在工作目录中乱七八糟。 我们没有讨论的另一件事是如何以组织代码和类的模块化方式创建大型系统。 例如,您可能想让不同目录中的代码生成可以彼此使用的类。 您可能还想将其他库中的类合并到自己的代码中。 我们将讨论满足这些需求的解决方案,以及构建系统和软件包。

注意 :本教程假定您正在使用某些版本的Unix。 如果您使用的是Windows,则应考虑使用Cygwin,也可以双启动计算机

注意 :在本教程中,我假设您使用的是简单的文本编辑器来修改文件。 但是,请注意,可以在更强大的集成开发人员环境(IDE)(例如Eclipse,IntelliJ和NetBeans)中使用您正在使用的常规设置。

设置Scalabha

我们将使用SBT,它可能是Scala最受欢迎的构建工具。 前面提到的Scalabha工具包使用SBT(版本0.11.0),因此我们将在Scalabha上下文中讨论SBT。
您需要做的第一件事是下载Scalabha v0.1.1,然后解压缩文件, 转到解压缩到的目录,并列出目录内容。

$ unzip scalabha-0.1.1-src.zip
Archive:  scalabha-0.1.1-src.zip
<lots of output>
$ cd scalabha-0.1.1
$ ls
CHANGES.txt README      build.sbt   project
LICENSE     bin         data        src

简要地,这些内容是:

  • README :一个文本文件,描述如何在您的计算机上安装Scalabha。
  • 许可证 :提供许可证的文本文件,它是Apache软件许可证2.0。
  • CHANGES.tx t:一个文本文件,描述对每个版本所做的修改(到目前为止还不多)。
  • build.sbt :一个文本文件,其中包含有关如何构建Scalabha的SBT指令
  • bin :一个包含scalabha脚本的目录,该目录将用于运行Scalabha构建系统中开发的应用程序以及运行SBT本身。 它还包含sbt-launch-0.11.0.jar,它是SBT类的瓶装软件包,使我们可以非常轻松地使用SBT。 还有一些其他文件,它们是与研究项目相关的Perl脚本,在这里并不重要。
  • data :一个包含英语和捷克语的词性标记数据的目录,构成本学期我的“计算语言学概论”课程第四次作业的基础。
  • project :包含单个文件“ plugins.sbt”的目录,该文件告诉SBT使用Assembly插件。 稍后再详细介绍。
  • src :最重要的目录-它包含Scalabha系统的源代码,在使用SBT时将在其中添加一些代码。

此时,您应该阅读自述文件并在计算机上进行Scalabha的设置,包括从源代码构建系统。 在本教程中,我将提供有关使用SBT和代码开发的更多详细信息,以补充和扩展README中提供的简要信息。

请注意,我将在下面引用环境变量SCALABHA_DIR 。 如自述文件中所指定,您应将此变量的值设置为解压缩Scalabha的位置。 例如,对我来说,此目录为〜/ devel / scalabha
提示 :为了避免每次打开新外壳时都需要设置环境变量的情况,可以在〜/ .profile (Mac,Cygwin)或〜/ .bash_aliases (Ubuntu)文件中设置环境变量。 例如,这在我的计算机上的配置文件中。

export SCALABHA_DIR=$HOME/devel/scalabha
export PATH=$PATH:$SCALABHA_DIR/bin

SBT:简单构建工具

这不是有关设置项目以使用SBT的教程,而是有关如何使用已为SBT设置的项目的教程。 因此,如果您正在寻找有关学习SBT的资源,那么您将主要找到帮助程序员为其项目配置SBT的资源。 这些可能会使您感到困惑(就配置而言,“简单构建工具”不再那么简单)。 使用它很简单,但是经验丰富的编码人员在使用SBT之类的技术时,可能会发现很多帮助。 在这里,我打算提供一些基础知识,以便您有一个更好的起点来开始使用SBT进行更多操作。

首先,Scalabha可能会有些混乱。 我没有让用户自己安装SBT,而是将SBT的jar文件放在Scalabha的bin目录中。 然后, scalabha可执行文件(在同一目录中)可以选择并使用它来运行SBT。 (我和我的学生已经以这种方式建立了许多Scala / Java项目,包括FogbowJuntoTextgrounderUpdown 。) scalabha可执行文件具有多个执行目标(稍后将对此进行详细介绍),其中一个是“ build ”。

当您调用scalabha的构建目标时,它将调用SBT并将您放入SBT界面。
在您的SCALABHA_DIR中执行以下操作

$ scalabha build
[info] Loading project definition from /Users/jbaldrid/devel/scalabha/project
[info] Set current project to Scalabha (in build file:/Users/jbaldrid/devel/scalabha/)
>

您可以通过下载SBT并按照SBT的说明运行它来达到相同的目的,但是此设置可以避免麻烦,并确保您获得正确的SBT版本。 值得指出的是,您不会认为Scalabha是SBT – SBT完全独立于Scalabha。

如果您对Scalabha设置有任何问题,可以在Scalabha Bitbucket网站上创建问题 。 那只是意味着我会通知您,您遇到了一些问题,希望能对您有所帮助。 并且,可能其他人也会遇到相同的问题,在这种情况下,您可能会在那找到答案。 这种设置的大多数问题是由于对环境变量的混淆以及对命令行工具的不熟悉所致。

用SBT编译

现在让我们实际使用SBT做一些事情。 如果您成功通过了README,您将已经完成了下一步,但是我将提供一些更多的细节。
因为您可能已经在执行自述文件的过程中运行了一些SBT操作,所以首先请运行“ clean ”操作,以使我们位于同一页面上。

> clean
[success] Total time: 0 s, completed Oct 26, 2011 10:18:08 AM

然后,运行“ compile ”操作。

> compile
[info] Updating {file:/Users/jbaldrid/devel/scalabha/}default-86efd0...
[info] Done updating.
[info] Compiling 13 Scala sources to /Users/jbaldrid/devel/scalabha/target/classes...
[success] Total time: 9 s, completed Oct 26, 2011 10:18:19 AM

在另一个外壳程序(意味着另一个命令行窗口)中,转到SCALABHA_DIR并列出目录的内容。 您会看到已经创建了两个新目录, lib_managedtarget 。 首先是从互联网上下载其他库并将它们放到Scalabha项目空间中的地方,以便可以轻松使用它们-暂时不用担心。 第二个是编译后的类文件所在的位置。 要查看一些示例类文件,请执行以下操作。

$ ls target/classes/opennlp/scalabha/postag/
BaselineTagger$$anonfun$tag$1.class
BaselineTagger.class
EnglishTagInfo$$anonfun$zipWithTag$1$1.class
<... many more class files ...>
RuleBasedTagger$$anonfun$tag$2.class
RuleBasedTagger$$anonfun$tagWord$1.class
RuleBasedTagger.class

这些是从以下源文件生成的。

$ ls src/main/scala/opennlp/scalabha/postag/
HmmTagger.scala PosTagger.scala

在文本编辑器中打开PosTagger.scala并进行查看-您将在target / classes目录中看到作为生成的类文件来源的类和对象定义。 基本上,SBT方便地处理了源文件和编译类文件的分离,这样我们就不会使类文件杂乱地占用我们的工作空间。

SBT如何知道类文件在哪里? 简单:配置为查看src / main / scala并编译在该目录下找到的每个.scala文件。 稍后,您将开始添加自己的scala文件,并能够作为Scalabha构建系统的一部分进行编译和运行。

接下来,在SBT提示符下,调用“ package ”操作。

> package
[info] Updating {file:/Users/jbaldrid/devel/scalabha/}default-86efd0...
[info] Done updating.
[info] Packaging /Users/jbaldrid/devel/scalabha/target/scalabha-0.1.1.jar ...
[info] Done packaging.
[success] Total time: 0 s, completed Oct 26, 2011 10:19:02 AM

在以前用于列出文件的shell提示中,列出目标目录的内容。

$ ls target/
cache              classes            scalabha-0.1.1.jar streams

您刚刚创建了scalabha-0.1.1.jar,这是Scalabha代码的瓶装版本,其他人可以在自己的库中使用。 扩展名“ jar”代表J ava Ar chive,它基本上只是一堆类文件的压缩集合。

Scalabha本身使用其他人提供的另一个支持库。 要查看Scalabha用作支持库的jar,请执行以下操作。

$ ls lib_managed/jars/*/*/*.jar
lib_managed/jars/jline/jline/jline-0.9.94.jar
lib_managed/jars/junit/junit/junit-3.8.1.jar
lib_managed/jars/org.apache.commons/commons-lang3/commons-lang3-3.0.1.jar
lib_managed/jars/org.clapper/argot_2.9.1/argot_2.9.1-0.3.5.jar
lib_managed/jars/org.clapper/grizzled-scala_2.9.1/grizzled-scala_2.9.1-1.0.8.jar
lib_managed/jars/org.scalatest/scalatest_2.9.0/scalatest_2.9.0-1.6.1.jar

当然,您可能仍然想知道在代码中“使用库”是什么意思。 在我们讨论软件包并实际开始自己编写一些代码之后,将对此进行更多介绍。

配套

通常将具有大量代码的项目组织到一个程序包中,该程序包具有一组子程序包,这些子程序包用于紧密协作的部分代码库。 在非常高的层次上,包只是确保我们拥有类的唯一完全限定名称的一种方式。 例如,Apache Commons Lang库和核心Scala库中有一个名为Range的类。 如果要在同一段代码中同时使用这两个类,那么显然存在名称冲突的问题。 幸运的是,它们包含在允许我们唯一引用它们的软件包中。

  • 范围在Apache Commons Lang中库org.apache.commons.lang3.Range
  • Scala中的范围scala.collection.immutable.Range

因此,当我们确实需要一起使用它们时,我们仍然能够做到无冲突。 实际上,您之前已经看过一些软件包名称,例如java.lang.String以及scala.collection.mutable.Mapscala.collection.immutable.Map之间的区别。

要在Scalabha中查看包和类,请在SBT中运行“ doc ”操作。

> doc
[info] Generating API documentation for main sources...
model contains 35 documentable templates
[info] API documentation generation successful.
[success] Total time: 7 s, completed Oct 26, 2011 10:22:23 AM

现在,将浏览器指向文件target / api / index.html 。 注意:这意味着先执行“打开文件”,然后转到SCALABHA_DIR ,然后定位api ,然后选择index.html 。 然后,您可以在Scalabha中浏览包和类。 例如,查看HmmTagger ,它位于包opennlp.scalabha.postag中 ,您将看到该类提供的一些字段和函数。

但是,您可能仍然想知道:无论如何我如何在代码中使用这些包和类? 我们通过导入语句来实现。 我们将通过创建自己的源代码并进行编译来探索这一点。

在SBT中创建和编译新代码

首先,我们首先要做一个简单的hello world应用程序,该应用程序在Scalabha的上下文中完成并使用包名。 通过执行以下命令来为此进行设置。

现在,将浏览器指向文件target / api / index.html 。 注意:这意味着先执行“打开文件”,然后转到SCALABHA_DIR,然后定位到api,再选择index.html。 然后,您可以在Scalabha中浏览包和类。 例如,查看HmmTagger(位于包opennlp.scalabha.postag中),您将看到该类提供的一些字段和函数。

但是,您可能仍然想知道:无论如何我如何在代码中使用这些包和类? 我们通过导入语句来实现。 我们将通过创建自己的源代码并进行编译来探索这一点。

在SBT中创建和编译新代码

首先,我们首先要做一个简单的hello world应用程序,该应用程序在Scalabha的上下文中完成并使用包名。 通过执行以下命令来为此进行设置。

$ cd $SCALABHA_DIR
$ cd src/main/scala/opennlp/
$ mkdir bcomposes

接下来,使用文本编辑器在src / main / scala / opennlp / bcomposes目录中创建具有以下内容的文件Hello.scala

package opennlp.bcomposes

object Hello {
  def main (args: Array[String]) = println("Hello, world!")
}

这就像上一教程中的hello world对象一样,但是现在它具有附加的程序包规范,该规范指示其完全限定名称为opennlp.bcomposes.Hello

因为Hello.scala的源代码位于src / main / scala目录的子目录中,所以我们现在可以使用SBT编译此文件。 确保保存Hello.scala ,然后返回到SBT提示符并键入“ compile ”。

> compile
[info] Compiling 1 Scala source to /Users/jbaldrid/devel/scalabha/target/classes...
[success] Total time: 1 s, completed Oct 26, 2011 10:35:15 AM

注意,它仅编译了一个Scala源:SBT已经在Scalabha中编译了其他源文件,因此它只需要编译刚刚保存的新源文件。

成功创建编译了opennlp.bcomposes.Hello对象之后,我们现在就可以运行它了。 scalabha可执行文件提供了一个“ 运行 ”目标,使您可以运行Scalabha构建设置中生成的任何代码。 在您的外壳中,键入以下内容。

$ scalabha run opennlp.bcomposes.Hello
Hello, world!

实际上,有很多东西可以确保您的新类包含在CLASSPATH中并可以以这种方式使用(有关详细信息,请参见bin / scalabha )。 这将简化您的工作。 简而言之,正确设置CLASSPATH是新开发人员的主要困惑之一。 这样,您可以继续前进,而不必担心本质上是什么管道问题。

现在,假设您要更改Hello对象的定义,以同时打印出命令行上提供的其他消息。 修改main方法看起来像这样。

def main (args: Array[String]) {
  println("Hello, world!")
  println(args(0))
}

现在保存它,并尝试运行它。

$ scalabha run opennlp.bcomposes.Hello Goodbye
Hello, world!

糟糕-没用吗? 对于刚从脚本转换为编译的学生,我只是强迫您直接进入一个常见的混乱点:您必须先编译才能使用它。 因此,在SBT中调用compile ,然后再次尝试该命令。

$ scalabha run opennlp.bcomposes.Hello Goodbye
Hello, world!
Goodbye

要查看在Scala代码中产生语法错误时发生的情况,请返回Hello.scala并更改main方法中的第一个print语句,以使其缺少最后一个引号:

println("Hello, world!)

现在回到SBT并再次编译以查看从Scala编译器获得的情书。

[info] Compiling 1 Scala source to /Users/jbaldrid/devel/scalabha/target/classes...
[error] /Users/jbaldrid/devel/scalabha/src/main/scala/opennlp/bcomposes/Hello.scala:5: unclosed string literal
[error]     println("Hello, world!)
[error]             ^
[error] /Users/jbaldrid/devel/scalabha/src/main/scala/opennlp/bcomposes/Hello.scala:7: ')' expected but '}' found.
[error]   }
[error]   ^
[error] two errors found
[error] {file:/Users/jbaldrid/devel/scalabha/}default-86efd0/compile:compile: Compilation failed
[error] Total time: 0 s, completed Oct 26, 2011 11:02:07 AM

编译尝试失败,您必须返回并对其进行修复。 但是请不要这样做。 在此写保存编译循环中,SBT有一个方便的方面,可以节省您的时间和精力:SBT允许触发执行动作,这意味着SBT可以在其关注的内容发生变化时自动执行动作。 编译操作关心源代码,因此它可以监视文件系统中的更改并在保存文件时自动重新编译。 为此,您只需在操作前面添加

在更正错误之前,键入〜compile到SBT中。 您将看到与以前相同的错误消息,但是不必担心。 SBT输出的最后一行将显示:

1. Waiting for source changes... (press enter to interrupt)

现在,再次转到Hello.scala ,添加引号,然后保存文件。 这会触发SBT中的编译操作,因此您会看到它自动编译,并显示一条成功消息。

[info] Compiling 1 Scala source to /Users/jbaldrid/devel/scalabha/target/classes...
[success] Total time: 0 s, completed Oct 26, 2011 11:02:49 AM
2. Waiting for source changes... (press enter to interrupt)

这是查看代码是否在编译时进行编译的好方法,只需很少的工作。 每次保存文件时,它都会让您知道是否有问题。 而且,您还可以使用scalabha 运行目标,并知道您正在使用最新的编译版本。

以这种方式开发代码时,您可以在SBT中调用“ doc ”操作,然后在浏览器中重新加载index.html页面,它将为您显示已创建内容的更新文档。 现在尝试一下,看看现在创建的opennlp.bcomposes包。

创建使用现有软件包的代码

现在,我们可以回到使用现有软件包中的代码的方式了。 过去(如果您已经看完所有这些教程),您会看到诸如import scala.io.Source之类的语句。 它来自标准的Scala库,因此任何Scala程序始终可以使用它。 但是,您也可以以类似的方式使用其他人开发的类,前提是您的CLASSPATH设置为可用。 这正是SBT为您所做的: src / main / scala子目录中定义的所有类都可以使用。

例如,将以下代码另存为src / main / scala / opennlp / bcomposes / TreeTest.scala 。 它为句子“我喜欢咖啡”构建标准的短语结构树。

package opennlp.bcomposes

import opennlp.scalabha.model.{Node,Value}

object TreeTest {

  def main (args: Array[String]) {
    val leaf1 = Value("I")
    val leaf2 = Value("like")
    val leaf3 = Value("coffee")
    val subjNpNode = Node("NP", List(leaf1))
    val verbNode = Node("V", List(leaf2))
    val objNpNode = Node("NP", List(leaf3))
    val vpNode = Node("VP", List(verbNode, objNpNode))
    val sentenceNode = Node("S", List(subjNpNode, vpNode))

    println("Printing the full tree:\n" + sentenceNode)
    println("\nPrinting the children of the VP node:\n" + vpNode.children)

    println("\nPrinting the yield of the full tree:\n" + sentenceNode.getTokens.mkString(" "))
    println("\nPrinting the yield of the VP node:\n" + vpNode.getTokens.mkString(" "))
  }

}

这里有几件事要注意。 顶部的import语句告诉Scala类NodeValue的完全合格的软件包名称。 您可能没有那么简洁地等效地编写如下内容。

import opennlp.scalabha.model.Node
import opennlp.scalabha.model.Value

或者,您可能省略了import语句,并在各处都写了全限定名称,例如:

val leaf1 = opennlp.scalabha.model.Value("I")

其次, NodeValue案例类。 我们稍后再讨论,但是现在,您只需要知道要创建NodeValue类的对象,就不必使用“ new ”关键字。

第三, 打印语句使用Scalabha API(应用程序编程接口)对对象执行有用的操作,例如打印出它们描述的树,打印节点的产量(它们覆盖的单词)等等。 您之前看过的Scalabha的scaladoc向您展示了这些功能,所以如果您还没有,请去看看。

请注意,如果您保留了触发的编译,SBT将自动编译TreeTest.scala 。 否则,请确保自己编译。 然后,运行它。

$ scalabha run opennlp.bcomposes.TreeTest
Printing the full tree:
Node(S,List(Node(NP,List(Value(I))), Node(VP,List(Node(V,List(Value(like))), Node(NP,List(Value(coffee)))))))

Printing the children of the VP node:
List(Node(V,List(Value(like))), Node(NP,List(Value(coffee))))

Printing the yield of the full tree:
I like coffee

Printing the yield of the VP node:
like coffee

制作并使用您自己的包装

通过以这种方式导入所需的类,可以根据需要使用它们来完成更多工作。 您可以使用Scalabha或随附的库中的任何类,包括您定义的任何类。 例如,执行以下操作。

$ cd $SCALABHA_DIR/src/main/scala/opennlp/bcomposes
$ mkdir person
$ mkdir music

现在,将上一教程中的Person另存person目录中的Person.scala 。 这又是代码(请注意package语句的添加)。

package opennlp.bcomposes.person

class Person (
  val firstName: String,
  val lastName: String,
  val age: Int,
  val occupation: String
) {

  def fullName: String = firstName + " " + lastName

  def greet (formal: Boolean): String = {
    if (formal)
      "Hello, my name is " + fullName + ". I'm a " + occupation + "."
    else
      "Hi, I'm " + firstName + "!"
  }

}

现在,将以下内容另存为Music目录中的RadioheadGreeting.scala

package opennlp.bcomposes.music

import opennlp.bcomposes.person.Person

object RadioheadGreeting {

  def main (args: Array[String]) {
    val thomYorke = new Person("Thom", "Yorke", 43, "musician")
    val johnnyGreenwood = new Person("Johnny", "Greenwood", 39, "musician")
    val colinGreenwood = new Person("Colin", "Greenwood", 41, "musician")
    val edObrien = new Person("Ed", "O'Brien", 42, "musician")
    val philSelway = new Person("Phil", "Selway", 44, "musician")
    val radiohead = List(thomYorke, johnnyGreenwood, colinGreenwood, edObrien, philSelway)
    radiohead.foreach(bandmember => println(bandmember.greet(false)))
  }

}

当我们之前进行编译教程时Person.scalaRadioheadGreeting.scala位于同一目录中,这使后者可以了解Person类。 现在它们在单独的程序包中,必须显式导入Person类。 一旦这样做,就可以像以前一样使用Person对象进行编码。

最后,要运行它,我们现在必须为RadioheadGreeting指定标准包名称。

$ scalabha run opennlp.bcomposes.music.RadioheadGreeting
Hi, I'm Thom!
Hi, I'm Johnny!
Hi, I'm Colin!
Hi, I'm Ed!
Hi, I'm Phil!

有关软件包名称及其与目录关系的注释

某些约定使程序包名称具有唯一性,这些约定通常可确保您不会发生冲突。 例如,我们正在使用opennlp.scalabhaopennlp.bcomposes ,我碰巧知道它们是唯一的。 相反,这些名称通常会包含完整的互联网域,例如org.apache.commonscom.cloudera.crunch 。 按照约定,我们将包(和子包)中的源文件放在反映名称的目录结构中。 因此,例如, opennlp.bcomposes.music.RadioheadGreeting位于目录src / main / scala / opennlp / bcomposes / music中 。 但是,值得注意的是,这并不是Scala的硬约束(与Java一样)。

使用构建系统还有很多其他内容,但这是我必须结束本次讨论的地方,希望能够充分理解核心概念并使我的学生有可能在词性标记上做作业并利用opennlp.scalabha.postag包!

参考: Scala中的入门程序员的第一步,来自BCG博客的JCG合作伙伴 Jason Baldridge的 第11部分

相关文章 :


翻译自: https://www.javacodegeeks.com/2011/11/scala-tutorial-sbt-scalabha-packages.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值