scala Scala应用程序结构

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5910791/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 02:59:49  来源:igfitidea点击:

Scala application structure

scala

提问by brainerazer

I am learning Scala now and I want to write some silly little app like a console Twitter client, or whatever. The question is, how to structure application on disk and logically. I know python, and there I would just create some files with classes and then import them in the main module like import util.sshor from tweets import Retweet(strongly hoping you wouldn't mind that names, they are just for reference). But how shouldI do this stuff using Scala? Also, I have not much experience with JVM and Java, so I am a complete newbie here.

我现在正在学习 Scala,我想编写一些愚蠢的小应用程序,比如控制台 Twitter 客户端,或者其他什么。问题是,如何在磁盘上和逻辑上构建应用程序。我知道python,在那里我只会创建一些带有类的文件,然后将它们导入到主模块中,例如import util.sshor from tweets import Retweet(强烈希望您不会介意这些名称,它们仅供参考)。但是我应该如何使用 Scala 来做这些事情呢?另外,我对 JVM 和 Java 没有太多经验,所以我在这里完全是新手。

回答by Daniel C. Sobral

I'm going to disagree with Jens, here, though not all that much.

我将不同意Jens,在这里,虽然不是那么多。

Project Layout

项目布局

My own suggestion is that you model your efforts on Maven's standard directory layout.

我自己的建议是您在Maven 的标准目录布局上对您的工作进行建模。

Previous versions of SBT (before SBT 0.9.x) would create it automatically for you:

以前版本的 SBT(SBT 0.9.x 之前)会自动为您创建:

dcs@ayanami:~$ mkdir myproject
dcs@ayanami:~$ cd myproject
dcs@ayanami:~/myproject$ sbt
Project does not exist, create new project? (y/N/s) y
Name: myproject
Organization: org.dcsobral
Version [1.0]: 
Scala version [2.7.7]: 2.8.1
sbt version [0.7.4]: 
Getting Scala 2.7.7 ...
:: retrieving :: org.scala-tools.sbt#boot-scala
    confs: [default]
    2 artifacts copied, 0 already retrieved (9911kB/134ms)
Getting org.scala-tools.sbt sbt_2.7.7 0.7.4 ...
:: retrieving :: org.scala-tools.sbt#boot-app
    confs: [default]
    15 artifacts copied, 0 already retrieved (4096kB/91ms)
[success] Successfully initialized directory structure.
Getting Scala 2.8.1 ...
:: retrieving :: org.scala-tools.sbt#boot-scala
    confs: [default]
    2 artifacts copied, 0 already retrieved (15118kB/160ms)
[info] Building project myproject 1.0 against Scala 2.8.1
[info]    using sbt.DefaultProject with sbt 0.7.4 and Scala 2.7.7
> quit
[info] 
[info] Total session time: 8 s, completed May 6, 2011 12:31:43 PM
[success] Build completed successfully.
dcs@ayanami:~/myproject$ find . -type d -print
.
./project
./project/boot
./project/boot/scala-2.7.7
./project/boot/scala-2.7.7/lib
./project/boot/scala-2.7.7/org.scala-tools.sbt
./project/boot/scala-2.7.7/org.scala-tools.sbt/sbt
./project/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.4
./project/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.4/compiler-interface-bin_2.7.7.final
./project/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.4/compiler-interface-src
./project/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.4/compiler-interface-bin_2.8.0.RC2
./project/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.4/xsbti
./project/boot/scala-2.8.1
./project/boot/scala-2.8.1/lib
./target
./lib
./src
./src/main
./src/main/resources
./src/main/scala
./src/test
./src/test/resources
./src/test/scala

So you'll put your source files inside myproject/src/main/scala, for the main program, or myproject/src/test/scala, for the tests.

因此,您会将源文件放在 中myproject/src/main/scala,用于主程序,或myproject/src/test/scala用于测试。

Since that doesn't work anymore, there are some alternatives:

由于这不再起作用,因此有一些替代方法:

giter8 and sbt.g8

giter8 和 sbt.g8

Install giter8, clone ymasory's sbt.g8template and adapt it to your necessities, and use that. See below, for example, this use of unmodified ymasory's sbt.g8 template. I think this is one of the best alternatives to starting new projects when you have a good notion of what you want in all your projects.

安装gter8,克隆 ymasory 的sbt.g8模板并使其适应您的需要,然后使用它。例如,请参见下文,使用未修改的 ymasory 的 sbt.g8 模板。我认为,当您对所有项目中想要的内容有一个很好的了解时,这是启动新项目的最佳选择之一。

$ g8 ymasory/sbt
project_license_url [http://www.gnu.org/licenses/gpl-3.0.txt]:
name [myproj]:
project_group_id [com.example]:
developer_email [[email protected]]:
developer_full_name [John Doe]:
project_license_name [GPLv3]:
github_username [johndoe]:

Template applied in ./myproj

$ tree myproj
myproj
├── build.sbt
├── LICENSE
├── project
│?? ├── build.properties
│?? ├── build.scala
│?? └── plugins.sbt
├── README.md
├── sbt
└── src
    └── main
        └── scala
            └── Main.scala

4 directories, 8 files

np plugin

np插件

Use softprops's np pluginfor sbt. In the example below, the plugin is configured on ~/.sbt/plugins/build.sbt, and its settings on ~/.sbt/np.sbt, with standard sbt script. If you use paulp's sbt-extras, you'd need to install these things under the right Scala version subdirectory in ~/.sbt, as it uses separate configurations for each Scala version. In practice, this is the one I use most often.

对 sbt使用 softprops 的np 插件。在下面的示例中,插件使用标准 sbt 脚本配置在 上~/.sbt/plugins/build.sbt,其设置在 上~/.sbt/np.sbt。如果您使用 paulp 的 sbt-extras,则需要将这些东西安装在 中正确的 Scala 版本子目录下~/.sbt,因为它为每个 Scala 版本使用单独的配置。在实践中,这是我最常使用的一种。

$ mkdir myproj; cd myproj
$ sbt 'np name:myproj org:com.example'
[info] Loading global plugins from /home/dcsobral/.sbt/plugins
[warn] Multiple resolvers having different access mechanism configured with same name 'sbt-plugin-releases'. To avoid conflict, Remove duplicate project resolvers (`resolvers`) or rename publishing resolver (`publishTo`).
[info] Set current project to default-c642a2 (in build file:/home/dcsobral/myproj/)
[info] Generated build file
[info] Generated source directories
[success] Total time: 0 s, completed Apr 12, 2013 12:08:31 PM
$ tree
.
├── build.sbt
├── src
│?? ├── main
│?? │?? ├── resources
│?? │?? └── scala
│?? └── test
│??     ├── resources
│??     └── scala
└── target
    └── streams
        └── compile
            └── np
                └── $global
                    └── out

12 directories, 2 files

mkdir

目录

You could simply create it with mkdir:

您可以简单地创建它mkdir

$ mkdir -p myproj/src/{main,test}/{resource,scala,java}
$ tree myproj
myproj
└── src
    ├── main
    │?? ├── java
    │?? ├── resource
    │?? └── scala
    └── test
        ├── java
        ├── resource
        └── scala

9 directories, 0 files

Source Layout

源布局

Now, about the source layout. Jens recommends following Java style. Well, the Java directory layout is a requirement-- in Java. Scala does not have the same requirement, so you have the option of following it or not.

现在,关于源布局。Jens 推荐遵循 Java 风格。嗯,Java 目录布局是必需的——在 Java 中。Scala 没有相同的要求,因此您可以选择是否遵循它。

If you do follow it, assuming the base package is org.dcsobral.myproject, then source code for that package would be put inside myproject/src/main/scala/org/dcsobral/myproject/, and so on for sub-packages.

如果您确实遵循它,假设基本包是org.dcsobral.myproject,那么该包的源代码将放在 里面myproject/src/main/scala/org/dcsobral/myproject/,对于子包,依此类推。

Two common ways of diverging from that standard are:

偏离该标准的两种常见方式是:

  • Omitting the base package directory, and only creating subdirectories for the sub-packages.

    For instance, let's say I have the packages org.dcsobral.myproject.model, org.dcsobral.myproject.viewand org.dcsobral.myproject.controller, then the directories would be myproject/src/main/scala/model, myproject/src/main/scala/viewand myproject/src/main/scala/controller.

  • Putting everything together. In this case, all source files would be inside myproject/src/main/scala. This is good enough for small projects. In fact, if you have no sub-projects, it is the same as above.

  • 省略基本包目录,只为子包创建子目录。

    例如,假设我有包org.dcsobral.myproject.model, org.dcsobral.myproject.viewand org.dcsobral.myproject.controller,那么目录将是myproject/src/main/scala/model, myproject/src/main/scala/viewand myproject/src/main/scala/controller

  • 把所有东西放在一起。在这种情况下,所有源文件都在myproject/src/main/scala. 这对于小型项目来说已经足够了。其实,如果你没有子项目,就和上面一样。

And this deals with directory layout.

这涉及目录布局。

File Names

文件名

Next, let's talk about files. In Java, the practice is separating each class in its own file, whose name will follow the name of the class. This is good enough in Scala too, but you have to pay attention to some exceptions.

接下来,让我们谈谈文件。在 Java 中,实践是将每个类分隔在其自己的文件中,其名称将跟随类的名称。这在 Scala 中也足够好,但您必须注意一些例外情况。

First, Scala has object, which Java does not have. A classand objectof the same name are considered companions, which has some practical implications, but onlyif they are in the same file. So, place companion classes and objects in the same file.

首先,Scala 有object,而 Java 没有。Aclassobject同名被认为是伴随,这具有一些实际意义,但前提是它们在同一个文件中。因此,将伴随类和对象放在同一个文件中。

Second, Scala has a concept known as sealed class(or trait), which limits subclasses (or implementing objects) to those declared in the same file. This is mostly done to create algebraic data types with pattern matching with exhaustiveness check. For example:

其次,Scala 有一个称为sealed class(or trait)的概念,它将子类(或实现objects)限制为在同一文件中声明的那些。这主要是为了创建具有详尽检查的模式匹配的代数数据类型。例如:

sealed abstract class Tree
case class Node(left: Tree, right: Tree) extends Tree
case class Leaf(n: Int) extends Tree

scala> def isLeaf(t: Tree) = t match {
     |     case Leaf(n: Int) => println("Leaf "+n)
     | }
<console>:11: warning: match is not exhaustive!
missing combination           Node

       def isLeaf(t: Tree) = t match {
                             ^
isLeaf: (t: Tree)Unit

If Treewas not sealed, then anyone could extend it, making it impossible for the compiler to know whether the match was exhaustive or not. Anyway, sealedclasses go together in the same file.

如果Tree不是sealed,那么任何人都可以扩展它,使编译器无法知道匹配是否详尽无遗。无论如何,sealed类都在同一个文件中。

Another naming convention is to name the files containing a package object(for that package) package.scala.

另一个命名约定是命名包含package object(for that package)的文件package.scala

Importing Stuff

进口东西

The most basic rule is that stuff in the same package see each other. So, put everything in the same package, and you don't need to concern yourself with what sees what.

最基本的规则是同一个包中的东西相互可见。所以,把所有的东西都放在同一个包里,你不需要关心什么是什么。

But Scala also have relative references and imports. This requires a bit of an explanation. Say I have the following declarations at the top of my file:

但是 Scala 也有相对引用和导入。这需要一些解释。假设我的文件顶部有以下声明:

package org.dcsobral.myproject
package model

Everything following will be put in the package org.dcsobral.myproject.model. Also, not only everything inside that package will be visible, but everything inside org.dcsobral.myprojectwill be visible as well. If I just declared package org.dcsobral.myproject.modelinstead, then org.dcsobral.myprojectwould not be visible.

以下所有内容都将放入包中org.dcsobral.myproject.model。此外,不仅该包内的所有内容都可见,而且包内的所有内容org.dcsobral.myproject也可见。如果我只是声明package org.dcsobral.myproject.model,那么org.dcsobral.myproject将不可见。

The rule is pretty simple, but it can confuse people a bit at first. The reason for this rule is relative imports. Consider now the following statement in that file:

规则很简单,但一开始可能会让人们有点困惑。这条规则的原因是相对进口。现在考虑该文件中的以下语句:

import view._

This import may be relative -- all imports can be relative unless you prefix it with _root_.. It can refer to the following packages: org.dcsobral.myproject.model.view, org.dcsobral.myproject.view, scala.viewand java.lang.view. It could also refer to an object named viewinside scala.Predef. Or it could be an absolute import refering to a package named view.

这个导入可能是相对的——所有的导入都可以是相对的,除非你给它加上前缀_root_.。它可以指以下软件包:org.dcsobral.myproject.model.vieworg.dcsobral.myproject.viewscala.viewjava.lang.view。它还可以引用名为viewinside的对象scala.Predef。或者它可能是引用名为view.

If more than one such package exists, it will pick one according to some precedence rules. If you needed to import something else, you can turn the import into an absolute one.

如果存在多个这样的包,它会根据一些优先规则选择一个。如果您需要导入其他内容,您可以将导入变成绝对导入。

This import makes everything inside the viewpackage (wherever it is) visible in its scope. If it happens inside a class, and objector a def, then the visibility will be restricted to that. It imports everything because of the ._, which is a wildcard.

此导入使view包内的所有内容(无论它在哪里)在其范围内可见。如果它发生在 a class、 andobject或 a 内def,那么可见性将仅限于此。它导入所有内容,因为._,这是一个通配符。

An alternative might look like this:

另一种可能如下所示:

package org.dcsobral.myproject.model
import org.dcsobral.myproject.view
import org.dcsobral.myproject.controller

In that case, the packagesviewand controllerwould be visible, but you'd have to name them explicitly when using them:

在这种情况下,viewcontroller将是可见的,但是在使用它们时必须明确命名它们:

def post(view: view.User): Node =

Or you could use further relative imports:

或者您可以使用进一步的相对导入:

import view.User

The importstatement also enable you to rename stuff, or import everything but something. Refer to relevant documentation about it for more details.

import语句还使您能够重命名内容,或导入除某些内容之外的所有内容。有关更多详细信息,请参阅有关它的相关文档。

So, I hope this answer all your questions.

所以,我希望这能回答你所有的问题。

回答by Jens Schauder

Scala supports and encourages the package structure of Java /JVM and pretty much the same recommendation apply:

Scala 支持并鼓励 Java /JVM 的包结构,并且几乎相同的建议适用:

  • mirror the package structure in the directory structure. This isn't necessary in Scala, but it helps to find your way around
  • use your inverse domain as a package prefix. For me that means everything starts with de.schauderhaft. Use something that makes sense for you, if you don't have you own domain
  • only put top level classes in one file if they are small and closely related. Otherwise stick with one class/object per file. Exceptions: companion objects go in the same file as the class. Implementations of a sealed class go into the same file.
  • if you app grows you might want to have something like layers and modules and mirror those in the package structure, so you might have a package structure like this: <domain>.<module>.<layer>.<optional subpackage>.
  • don't have cyclic dependencies on a package, module or layer level
  • 在目录结构中镜像包结构。这在 Scala 中不是必需的,但它有助于找到解决方法
  • 使用您的反向域作为包前缀。对我来说,这意味着一切都从 de.schauderhaft 开始。如果您没有自己的域名,请使用对您有意义的内容
  • 如果顶级类很小且密切相关,则只将它们放在一个文件中。否则坚持每个文件一个类/对象。例外:伴随对象与类位于同一文件中。密封类的实现进入同一个文件。
  • 如果您的应用程序增长,您可能想要像层和模块这样的东西,并在包结构中镜像它们,所以你可能有这样的包结构:<domain>.<module>.<layer>.<optional subpackage>.
  • 对包、模块或层级没有循环依赖