Scala структура приложения

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

Ответ 1

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

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

Мое собственное предложение состоит в том, что вы моделируете свои усилия на стандартном расположении каталога Maven.

Предыдущие версии SBT (до SBT 0.9.x) создавали бы это автоматически для вас:

[email protected]:~$ mkdir myproject
[email protected]:~$ cd myproject
[email protected]:~/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.
[email protected]:~/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, clone 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

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

Вы можете просто создать его с помощью 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 нет. A 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 также позволяет вам переименовывать материал или импортировать все, кроме чего-то. Более подробную информацию см. В соответствующей документации.

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

Ответ 2

Scala поддерживает и поощряет структуру пакета Java/JVM и в значительной степени применяется одна и та же рекомендация:

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