Obiekty pakietu

92

Czym są obiekty pakietu, nie tyle koncepcja, ile ich użycie?

Próbowałem uzyskać działający przykład i jedyna forma, jaką otrzymałem, była następująca:

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)
        }
    }
}

Obserwacje, które poczyniłem do tej pory to:

package object _root_ { ... }

jest niedozwolone (co jest rozsądne),

package object x.y { ... }

jest również niedozwolone.

Wydaje się, że obiekt pakietu musi być zadeklarowany w bezpośrednim pakiecie nadrzędnym, a jeśli jest napisany jak wyżej, wymagany jest formularz deklaracji pakietu rozdzielanego nawiasami klamrowymi.

Czy są w powszechnym użyciu? Jeśli tak to jak?

Don Mackenzie
źródło
1
@Brent, to świetny zasób, nie tylko dla artykułu o obiekcie pakietu. Słyszałem o autorze, ale nie zdawałem sobie sprawy, że napisał tę trasę koncertową Scala, dzięki.
Don Mackenzie

Odpowiedzi:

128

Zwykle umieściłbyś obiekt pakietu w oddzielnym pliku o nazwie package.scala w pakiecie, któremu odpowiada. Możesz także użyć składni pakietu zagnieżdżonego, ale jest to dość nietypowe.

Głównym przypadkiem użycia obiektów pakietu jest sytuacja, w której potrzebujesz definicji w różnych miejscach wewnątrz pakietu, a także poza pakietem, gdy używasz interfejsu API zdefiniowanego przez pakiet. Oto przykład:

// 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 = // ...

}

Teraz definicje wewnątrz tego obiektu pakietu są dostępne w całym pakiecie foo.bar. Ponadto definicje są importowane, gdy importuje się ktoś spoza tego pakietu foo.bar._.

W ten sposób możesz zapobiec wymaganiu od klienta API wykonywania dodatkowych importów, aby efektywnie korzystać z biblioteki - np. W scala-swing musisz napisać

import swing._
import Swing._

mieć wszystkie podobne dobroci onEDTi ukryte konwersje z Tuple2na Dimension.

Moritz
źródło
13
Uwaga: przeciążanie metod nie działa w obiektach pakietu.
retronim
Nie wiem, dlaczego wybrano obiekt pakietu, który powinien być zdefiniowany o jeden poziom wyżej w hierarchii pakietów. Np. Oznacza to, że musisz zanieczyścić pakiet wirtualny orglub compakiet najwyższego poziomu swoim obiektem pakietu, jeśli chcesz, aby należał on do twojego własnego pakietu głównego, np org.foo. Uważam, że zezwolenie na umieszczenie definicji bezpośrednio pod pakietem, którego powinna być częścią, byłoby nieco bardziej odpowiednim interfejsem API w języku.
matanster
Należy pamiętać, że od co najmniej 2,10 i powyżej Scala przeciążenia czyni pracę w obiektach pakietów.
Jasper-M
58

Chociaż odpowiedź Moritza jest trafna, jedną dodatkową rzeczą, na którą należy zwrócić uwagę, jest to, że obiekty pakietu są obiektami. Oznacza to między innymi, że możesz budować je na podstawie cech, korzystając z dziedziczenia mieszanego. Przykład Moritza można zapisać jako

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 = // ...

}

W tym przypadku wersjonowanie jest cechą abstrakcyjną, która mówi, że obiekt pakietu musi mieć metodę "wersji", podczas gdy aliasy Joda i aliasy Java są konkretnymi cechami zawierającymi przydatne aliasy typów. Wszystkie te cechy można ponownie wykorzystać w wielu różnych obiektach opakowań.

Dave Griffith
źródło
Cały temat bardzo się otwiera i wydaje się, że jest w pełni wykorzystany, dzięki za kolejny bogaty przykład.
Don Mackenzie
1
ale nie można ich używać jako walców, więc tak naprawdę nie są obiektami
Eduardo Pareja Tobes
7

Mogłeś zrobić gorzej niż udać się prosto do źródła. :)

https://lampsvn.epfl.ch/trac/scala/browser/scala/trunk/src/library/scala/package.scala

https://lampsvn.epfl.ch/trac/scala/browser/scala/trunk/src/library/scala/collection/immutable/package.scala

Alex Cruise
źródło
@Alex Cruise, dzięki, wydaje się to sugerować, że potrzebują oddzielnej jednostki kompilacji (która może obejść ograniczenia dotyczące pakietów rozdzielonych nawiasami klamrowymi). Problem w tym, że wolę solidne porady dla użytkowników niż własne przypuszczenia, jak ich używać.
Don Mackenzie
5

Głównym przypadkiem użycia obiektów pakietu jest sytuacja, w której potrzebujesz definicji w różnych miejscach wewnątrz pakietu, a także poza pakietem, gdy używasz interfejsu API zdefiniowanego przez pakiet.

Inaczej jest w przypadku Scali 3 , która ma zostać wydana w połowie 2020 roku, oparta na Dotty , jak tutaj :

Definicje najwyższego poziomu

Na najwyższym poziomie można zapisać wszystkie rodzaje definicji.
Obiekty pakietu nie są już potrzebne, zostaną wycofane.

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
źródło
Dzięki @VonC, naprawdę nie mogę się doczekać Scala 3 z tego i wielu innych powodów. Nie korzystałem zbytnio z obiektów pakietów, ale jestem pewien, że użyję definicji najwyższego poziomu.
Don Mackenzie