Пакетні об'єкти


92

Що таке об’єкти пакету, не стільки концепція, скільки їх використання?

Я намагався отримати приклад роботи, і єдиною формою, якою я зайнявся, була така:

package object investigations {
    val PackageObjectVal = "A package object val"
}

package investigations {

    object PackageObjectTest {
        def main(args: Array[String]) {
            println("Referencing a package object val: " + PackageObjectVal)
        }
    }
}

Досі я спостерігав:

package object _root_ { ... }

заборонено (що є розумним),

package object x.y { ... }

також заборонено.

Здається, що об'єкт пакета повинен бути оголошений у безпосередньому батьківському пакунку, і, якщо написано, як зазначено вище, потрібна форма декларації пакета з розділеними фігурними дужками.

Чи є вони загальновживаними? Якщо так, то як?



1
@Brent, це чудовий ресурс, а не лише для статті об’єкта пакета. Я чув про автора, але не розумів, що він написав цю подорож Scala, дякую.
Дон Маккензі,

Відповіді:


128

Зазвичай ви розміщуєте об'єкт пакету в окремому файлі, що викликається package.scalaв пакеті, якому він відповідає. Ви також можете використовувати синтаксис вкладеного пакету, але це досить незвично.

Основний випадок використання об’єктів пакета - це коли вам потрібні визначення в різних місцях усередині пакета, а також поза пакетом, коли ви використовуєте API, визначений пакетом. Ось приклад:

// file: foo/bar/package.scala

package foo

package object bar {

  // package wide constants:
  def BarVersionString = "1.0"

  // or type aliases
  type StringMap[+T] = Map[String,T]

  // can be used to emulate a package wide import
  // especially useful when wrapping a Java API
  type DateTime = org.joda.time.DateTime

  type JList[T] = java.util.List[T]

  // Define implicits needed to effectively use your API:
  implicit def a2b(a: A): B = // ...

}

Тепер визначення всередині цього об'єкта пакету доступні всередині пакета foo.bar. Крім того, визначення імпортуються, коли хтось за межами цього пакету імпортує foo.bar._.

Таким чином ви можете запобігти вимаганню від клієнта API видавати додаткові імпорти для ефективного використання вашої бібліотеки - наприклад, у scala-swing вам потрібно написати

import swing._
import Swing._

мати всі добрі, як onEDTі неявні перетворення від Tuple2до Dimension.


13
Слово обережності: перевантаження методу не працює в об’єктах пакета.
ретронім

Мене б’є, чому було обрано, що об’єкт пакета повинен визначатися на один рівень вище за ієрархію пакета. Наприклад, це означає, що вам потрібно забруднити віртуальний пакет orgабо comпакет верхнього рівня своїм об’єктом пакета, якщо ви хочете, щоб він належав до вашого власного кореневого пакету, наприклад org.foo. Я вважаю, що дозволити визначення бути безпосередньо під пакетом, до якого воно мало б входити - було б трохи більш правильним мовним інтерфейсом api.
matanster

58

Незважаючи на те, що відповідь Моріца спогадна, ще одне, на що слід звернути увагу, це те, що об'єкти пакунка є об'єктами. Окрім іншого, це означає, що ви можете будувати їх за рисами, використовуючи змішане успадкування. Приклад Моріца можна записати як

package object bar extends Versioning 
                          with JodaAliases 
                          with JavaAliases {

  // package wide constants:
  override val version = "1.0"

  // or type aliases
  type StringMap[+T] = Map[String,T]

  // Define implicits needed to effectively use your API:
  implicit def a2b(a: A): B = // ...

}

Тут Версія - це абстрактна ознака, яка говорить, що об'єкт пакета повинен мати метод "версія", тоді як JodaAliases і JavaAliases - це конкретні риси, що містять зручні псевдоніми типу. Всі ці риси можуть бути використані багато разними об'єктами пакету.


Вся тема відкривається багато, і вона, здається, звикла до повного потенціалу, дякую за ще один багатий приклад.
Дон Маккензі,

1
але їх не можна використовувати як вали, тому вони насправді не є об'єктами
Едуардо Пареха Тобес

7

@Alex Cruise, дякую, це, мабуть, говорить про те, що їм потрібен окремий блок компіляції (який, можливо, обходить обмеження пакету з обмеженими фігурними дужками). Проблема в тому, що я хочу отримати вагомі поради користувачів, а не власні здогадки про те, як ними користуватися.
Дон Маккензі

5

Основний випадок використання об’єктів пакета - це коли вам потрібні визначення в різних місцях усередині пакета, а також поза пакетом, коли ви використовуєте API, визначений пакетом.

З Scala 3 , який планується випустити в середині 2020 року на базі Dotty , не так, як тут :

Визначення верхнього рівня

Різні визначення можна писати на верхньому рівні.
Пакетні об’єкти більше не потрібні, вони будуть поступово скасовані.

package p 

type Labelled[T] = (String, T) 
val a: Labelled[Int] = ("count", 1) 
def b = a._2 
def hello(name: String) = println(i"hello, $name)

Дякую @VonC, я дуже чекаю Scala 3 з цієї та багатьох інших причин. Я не надто використовував об'єкти пакетів, але я впевнений, що буду використовувати визначення вищого рівня.
Дон Маккензі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.