Scala应用程序结构

bra*_*zer 61 scala

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

Dan*_*ral 112

我不同意Jens,虽然不是那么多.

项目布局

我自己的建议是你在Maven的标准目录布局上模拟你的工作.

以前版本的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
Run Code Online (Sandbox Code Playgroud)

因此,您将把源文件放在myproject/src/main/scala主程序中,或者myproject/src/test/scala放在测试中.

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

giter8和sbt.g8

安装giter8,克隆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 [john.doe@example.com]:
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
Run Code Online (Sandbox Code Playgroud)

np插件

使用softprops的sp 插件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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

来源布局

现在,关于源布局.Jens建议遵循Java风格.嗯,Java目录布局是一项要求 - 在Java中.Scala没有相同的要求,因此您可以选择是否遵循它.

如果你遵循它,假设基础包是org.dcsobral.myproject,那么该包的源代码将被放入内部myproject/src/main/scala/org/dcsobral/myproject/,以此类推子包.

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

  • 省略基本包目录,仅创建子包的子目录.

    举例来说,假设我有包org.dcsobral.myproject.model,org.dcsobral.myproject.vieworg.dcsobral.myproject.controller,那么目录将是myproject/src/main/scala/model,myproject/src/main/scala/viewmyproject/src/main/scala/controller.

  • 将所有东西放在一起.在这种情况下,所有源文件都在里面myproject/src/main/scala.这对小型项目来说已经足够了.实际上,如果您没有子项目,则与上述相同.

这涉及目录布局.

文件名

接下来,我们来谈谈文件.在Java中,实践是将每个类分隔在自己的文件中,该文件的名称将遵循类的名称.这在Scala中也足够好,但你必须注意一些例外.

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

其次,Scala有一个称为sealed class(或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
Run Code Online (Sandbox Code Playgroud)

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

另一个命名约定是命名包含package object(对于该包)的文件package.scala.

导入东西

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

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

package org.dcsobral.myproject
package model
Run Code Online (Sandbox Code Playgroud)

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

规则很简单,但最初可能会让人感到困惑.此规则的原因是相对导入.现在考虑该文件中的以下语句:

import view._
Run Code Online (Sandbox Code Playgroud)

此导入可能是相对的 - 除非您使用前导,否则所有导入都可以是相对的_root_..它可以指以下软件包:org.dcsobral.myproject.model.view,org.dcsobral.myproject.view,scala.viewjava.lang.view.它也可以引用一个名为viewinside 的对象scala.Predef.或者它可以是一个绝对导入,引用一个名为的包view.

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

此导入使view包中的所有内容(无论它在何处)在其范围内可见.如果它发生在a class和/ object或a中def,则可见性将限制在此范围内.它导入所有东西,因为._它是一个通配符.

替代方案可能如下所示:

package org.dcsobral.myproject.model
import org.dcsobral.myproject.view
import org.dcsobral.myproject.controller
Run Code Online (Sandbox Code Playgroud)

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

def post(view: view.User): Node =
Run Code Online (Sandbox Code Playgroud)

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

import view.User
Run Code Online (Sandbox Code Playgroud)

import语句还允许您重命名内容,或导入除了某些内容之外的所有内容.有关详细信息,请参阅相关文档.

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


Jen*_*der 13

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

  • 镜像目录结构中的包结构.这在Scala中不是必需的,但它有助于找到你的方法
  • 使用逆域作为包前缀.对我而言,这意味着一切都始于de.schauderhaft.如果您没有自己的域名,请使用对您有意义的内容
  • 如果顶级类很小并且密切相关,则只将顶级类放在一个文件中.否则每个文件都有一个类/对象.例外:伴侣对象与该类位于同一文件中.密封类的实现进入同一个文件.
  • 如果你的应用程序增长,你可能希望有类似图层和模块的东西,并在包结构中镜像那些,所以你可能有这样的包结构:<domain>.<module>.<layer>.<optional subpackage>.
  • 在包,模块或层级上没有循环依赖关系

  • 使用它的类添加密封的case类和对象.我会删除我的答案.这个更好. (3认同)