Структура приложения Scala - PullRequest
61 голосов
/ 06 мая 2011

Сейчас я изучаю Scala и хочу написать какое-нибудь глупое маленькое приложение, такое как консольный клиент Twitter или что-то еще. Вопрос в том, как структурировать приложение на диске и логически. Я знаю python, и там я бы просто создал несколько файлов с классами, а затем импортировал их в основной модуль, например import util.ssh или from tweets import Retweet (очень надеюсь, что вы не будете возражать против этих имен, они просто для справки). Но как должен делать это с помощью Scala? Кроме того, у меня мало опыта работы с JVM и Java, поэтому я здесь новичок.

Ответы [ 2 ]

112 голосов
/ 06 мая 2011

Я собираюсь не согласиться с Йенсом , здесь, хотя и не так сильно.

Макет проекта

Мое собственное предложение состоит в том, чтобы вы смоделировали свои усилия на Стандартном макете каталогов 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 и адаптируйте его под свои нужды, и используйте его. См. Ниже, например, это использование шаблона sbt.g8 неизмененного ymasory's. Я думаю, что это одна из лучших альтернатив для запуска новых проектов, когда у вас есть четкое представление о том, что вы хотите во всех своих проектах.

$ 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 plugin

Используйте плагин softprops np для sbt. В приведенном ниже примере плагин настроен на ~/.sbt/plugins/build.sbt, а его настройки на ~/.sbt/np.sbt со стандартным сценарием sbt. Если вы используете sbt-extras от paulp, вам нужно будет установить эти вещи в нужном подкаталоге версии 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

Вы можете просто создать его с помощью 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

Исходный макет

Теперь по поводу макета источника. Дженс рекомендует придерживаться стиля Java. Что ж, макет каталога Java - это требование - в Java. В Scala нет того же требования, поэтому вы можете выполнить его или нет.

Если вы выполните его, предполагая, что базовый пакет равен org.dcsobral.myproject, то исходный код для этого пакета будет помещен в myproject/src/main/scala/org/dcsobral/myproject/, и так далее для подпакетов.

Два распространенных способа отклонения от этого стандарта:

  • Пропуск каталога базового пакета и создание только подкаталогов для подпакетов.

    Например, допустим, у меня есть пакеты org.dcsobral.myproject.model, org.dcsobral.myproject.view и org.dcsobral.myproject.controller, тогда каталоги будут myproject/src/main/scala/model, myproject/src/main/scala/view и myproject/src/main/scala/controller.

  • Собираем все вместе. В этом случае все исходные файлы будут внутри myproject/src/main/scala. Этого достаточно для небольших проектов. Фактически, если у вас нет подпроектов, это то же самое, что и выше.

И это касается макета каталога.

Имена файлов

Далее поговорим о файлах. В Java практика отделяет каждый класс в своем собственном файле, имя которого будет следовать за именем класса. Это достаточно хорошо и в Scala, но вы должны обратить внимание на некоторые исключения.

Во-первых, у Scala есть object, которого нет у Java. class и object с одинаковыми именами считаются компаньонами , что имеет некоторые практические последствия, но только , если они находятся в одном файле. Итак, поместите сопутствующие классы и объекты в один файл.

Во-вторых, в Scala есть концепция, известная как sealed class (или trait), которая ограничивает подклассы (или реализует object s) теми, которые объявлены в том же файле. В основном это делается для создания алгебраических типов данных с сопоставлением с шаблоном с проверкой на полноту. Например:

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

Если Tree не было sealed, тогда любой мог бы его расширить, не дав компилятору узнать, было ли совпадение исчерпывающим или нет. В любом случае, sealed классы объединяются в одном файле.

Другое соглашение об именах - именовать файлы, содержащие package object (для этого пакета) package.scala.

Импорт материалов

Самое основное правило - вещи в одном пакете видят друг друга. Итак, поместите все в одну упаковку, и вам не нужно беспокоиться о том, что видит.

Но у Scala также есть относительные ссылки и импорт. Это требует немного объяснения. Скажем, у меня есть следующие объявления вверху моего файла:

package org.dcsobral.myproject
package model

Все следующее будет помещено в пакет org.dcsobral.myproject.model.Кроме того, будет видно не только все, что находится внутри этого пакета, но и все, что находится внутри org.dcsobral.myproject.Если бы я просто объявил package org.dcsobral.myproject.model, тогда org.dcsobral.myproject не было бы видно.

Правило довольно простое, но поначалу оно может немного запутать людей.Причиной этого правила является относительный импорт.Теперь рассмотрим следующее утверждение в этом файле:

import view._

Этот импорт может быть относительным - все операции импорта могут быть относительными, если вы не добавите префикс _root_..Это может относиться к следующим пакетам: org.dcsobral.myproject.model.view, org.dcsobral.myproject.view, scala.view и java.lang.view.Он также может ссылаться на объект с именем view внутри scala.Predef.Или это может быть абсолютный импорт, ссылающийся на пакет с именем view.

Если существует более одного такого пакета, он выберет один в соответствии с некоторыми правилами приоритета.Если вам нужно было импортировать что-то еще, вы можете превратить импорт в абсолютный.

Этот импорт делает все внутри пакета view (где бы он ни находился) видимым в своей области видимости.Если это происходит внутри class и object или def, то видимость будет ограничена этим.Он импортирует все из-за ._, который является подстановочным знаком.

Альтернатива может выглядеть следующим образом:

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

В этом случае пакетов view и controller будут видны, но вы должны будете назвать их явно при использовании:

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

Или вы можете использовать дальнейшие относительные импорты:

import view.User

import оператор также позволяет вам переименовывать вещи или импортировать все, кроме чего-то.Более подробную информацию см. В соответствующей документации.

Итак, я надеюсь, что это ответит на все ваши вопросы.

13 голосов
/ 06 мая 2011

Scala поддерживает и поощряет структуру пакетов Java / JVM, и в основном применяется та же рекомендация:

  • отражает структуру пакета в структуре каталогов. Это не обязательно в Scala, но это поможет вам разобраться
  • используйте ваш обратный домен в качестве префикса пакета. Для меня это означает, что все начинается с de.schauderhaft. Используйте то, что имеет смысл для вас, если у вас нет собственного домена
  • помещайте классы верхнего уровня в один файл, только если они маленькие и тесно связаны между собой. В противном случае придерживайтесь одного класса / объекта на файл. Исключения: сопутствующие объекты находятся в том же файле, что и класс. Реализации запечатанного класса входят в один и тот же файл.
  • если ваше приложение растет, вы можете захотеть иметь что-то вроде слоев и модулей и отразить их в структуре пакета, поэтому у вас может быть такая структура пакета:
  • не имеет циклических зависимостей на уровне пакета, модуля или уровня
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...