Scala 애플리케이션 구조
저는 지금 Scala를 배우고 있으며 콘솔 Twitter 클라이언트 같은 어리석은 작은 앱을 작성하고 싶습니다. 문제는 디스크에서 애플리케이션을 논리적으로 구성하는 방법입니다. 나는 파이썬을 알고 있고, 거기에서 클래스로 일부 파일을 만든 다음 import util.ssh
또는 같은 주 모듈에서 가져옵니다 from tweets import Retweet
(강하게 이름이 마음에 들지 않기를 바랍니다. 참조 용입니다). 하지만 Scala를 사용하여 어떻게 해야 합니까? 또한 저는 JVM과 Java에 대한 경험이 많지 않기 때문에 여기에서 완전히 초보자입니다.
나는 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
따라서 소스 파일을 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
np 플러그인
sbt 에는 softprops의 np 플러그인 을 사용하십시오 . 아래 예에서 플러그인은 표준 sbt 스크립트를 사용하여 ~/.sbt/plugins/build.sbt
에 구성 되고 해당 설정 은에 구성되어 ~/.sbt/np.sbt
있습니다. paulp의 sbt-extras를 사용하는 경우 ~/.sbt
각 Scala 버전에 대해 별도의 구성을 사용하므로 에서 올바른 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
다음으로 간단히 만들 수 있습니다 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
소스 레이아웃
이제 소스 레이아웃에 대해. 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.
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.view
andorg.dcsobral.myproject.controller
, then the directories would bemyproject/src/main/scala/model
,myproject/src/main/scala/view
andmyproject/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.
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.
First, Scala has object
, which Java does not have. A class
and object
of the same name are considered companions, which has some practical implications, but only if they are in the same file. So, place companion classes and objects in the same file.
Second, Scala has a concept known as sealed class
(or trait
), which limits subclasses (or implementing object
s) to those declared in the same file. This is mostly done to create algebraic data types with pattern matching with exhaustiveness check. For example:
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 Tree
was not sealed
, then anyone could extend it, making it impossible for the compiler to know whether the match was exhaustive or not. Anyway, sealed
classes go together in the same file.
Another naming convention is to name the files containing a 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:
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.myproject
will be visible as well. If I just declared package org.dcsobral.myproject.model
instead, then org.dcsobral.myproject
would not be visible.
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.view
and java.lang.view
. It could also refer to an object named view
inside scala.Predef
. Or it could be an absolute import refering to a package named 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 view
package (wherever it is) visible in its scope. If it happens inside a class
, and object
or a def
, then the visibility will be restricted to that. It imports everything because of the ._
, which is a wildcard.
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 packages view
and controller
would be visible, but you'd have to name them explicitly when using them:
def post(view: view.User): Node =
Or you could use further relative imports:
import view.User
The import
statement also enable you to rename stuff, or import everything but something. Refer to relevant documentation about it for more details.
So, I hope this answer all your questions.
Scala supports and encourages the package structure of Java /JVM and pretty much the same recommendation apply:
- 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
참고URL : https://stackoverflow.com/questions/5910791/scala-application-structure
'Development Tip' 카테고리의 다른 글
약어에 대한 C # 명명 규칙 (0) | 2020.11.30 |
---|---|
쉘 스크립트를 통해 파일의 문자 수 계산 (0) | 2020.11.30 |
MySQL의 FOR UPDATE 잠금을 사용할 때 정확히 잠긴 것은 무엇입니까? (0) | 2020.11.30 |
중복 된 'row.names'는 허용되지 않습니다. 오류 (0) | 2020.11.30 |
MongoDB의 정확한 요소 배열에서 필드 업데이트 (0) | 2020.11.30 |