Paketobjekte

92

Was sind Paketobjekte, nicht so sehr das Konzept, sondern ihre Verwendung?

Ich habe versucht, ein Beispiel zum Laufen zu bringen, und das einzige Formular, das ich zur Arbeit bekam, war wie folgt:

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

Beobachtungen, die ich bisher gemacht habe, sind:

package object _root_ { ... }

ist nicht erlaubt (was vernünftig ist),

package object x.y { ... }

ist auch nicht erlaubt.

Es scheint, dass ein Paketobjekt im unmittelbaren übergeordneten Paket deklariert werden muss und, falls wie oben beschrieben, das durch Klammern getrennte Paketdeklarationsformular erforderlich ist.

Sind sie gebräuchlich? Wenn das so ist, wie?

Don Mackenzie
quelle
1
@Brent, dies ist eine großartige Ressource, nicht nur für den Paketobjektartikel. Ich habe von dem Autor gehört, aber nicht bemerkt, dass er diese Scala-Tour geschrieben hat, danke.
Don Mackenzie

Antworten:

128

Normalerweise würden Sie Ihr Paketobjekt in einer separaten Datei ablegen, die package.scalain dem Paket aufgerufen wird , dem es entspricht. Sie können auch die verschachtelte Paketsyntax verwenden, dies ist jedoch recht ungewöhnlich.

Der Hauptanwendungsfall für Paketobjekte besteht darin, dass Sie Definitionen an verschiedenen Stellen innerhalb Ihres Pakets sowie außerhalb des Pakets benötigen, wenn Sie die vom Paket definierte API verwenden. Hier ist ein Beispiel:

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

}

Jetzt sind die Definitionen in diesem Paketobjekt im gesamten Paket verfügbar foo.bar. Außerdem werden die Definitionen importiert, wenn jemand außerhalb dieses Pakets importiert foo.bar._.

Auf diese Weise können Sie verhindern, dass der API-Client zusätzliche Importe ausgibt, um Ihre Bibliothek effektiv zu nutzen - z. B. in Scala-Swing müssen Sie schreiben

import swing._
import Swing._

all die Güte wie onEDTund implizite Umwandlungen von Tuple2zu haben Dimension.

Moritz
quelle
13
Achtung: Das Überladen von Methoden funktioniert in Paketobjekten nicht.
Retronym
Schlägt mich, warum ausgewählt wurde, dass das Paketobjekt eine Ebene höher in der Pakethierarchie definiert werden soll. Dies bedeutet beispielsweise, dass Sie das virtuelle Paket orgoder das comPaket der obersten Ebene mit Ihrem Paketobjekt verschmutzen müssen, wenn Sie möchten, dass es zu Ihrem eigenen Stammpaket gehört, z org.foo. Ich finde, dass es etwas besser gewesen wäre, die Definition direkt unter dem Paket zu haben, zu dem sie gehören sollte.
Matanster
58

Während Moritz 'Antwort genau richtig ist, ist eine zusätzliche Sache zu beachten, dass Paketobjekte Objekte sind. Dies bedeutet unter anderem, dass Sie sie mithilfe der Mix-In-Vererbung aus Merkmalen aufbauen können. Moritz 'Beispiel könnte geschrieben werden als

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

}

Hier ist die Versionierung ein abstraktes Merkmal, das besagt, dass das Paketobjekt eine "Versions" -Methode haben muss, während JodaAliases und JavaAliases konkrete Merkmale sind, die handliche Typ-Aliase enthalten. Alle diese Eigenschaften können von vielen verschiedenen Paketobjekten wiederverwendet werden.

Dave Griffith
quelle
Das ganze Thema öffnet sich sehr und es scheint sein volles Potenzial auszuschöpfen, danke für ein weiteres reichhaltiges Beispiel.
Don Mackenzie
1
aber sie können nicht als vals verwendet werden, also sind sie nicht wirklich Objekte
Eduardo Pareja Tobes
7

Sie könnten es schlimmer machen, als direkt zur Quelle zu gehen. :) :)

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
quelle
@Alex Cruise, danke, dies scheint darauf hinzudeuten, dass sie eine separate Kompilierungseinheit benötigen (was möglicherweise die durch Klammern begrenzte Paketbeschränkung umgeht). Das Problem ist, dass ich eher solide Benutzerratschläge als meine eigene Vermutung über deren Verwendung haben möchte.
Don Mackenzie
5

Der Hauptanwendungsfall für Paketobjekte besteht darin, dass Sie Definitionen an verschiedenen Stellen innerhalb Ihres Pakets sowie außerhalb des Pakets benötigen, wenn Sie die vom Paket definierte API verwenden.

Nicht so bei Scala 3 , das Mitte 2020 auf Basis von Dotty veröffentlicht werden soll , wie hier :

Toplevel-Definitionen

Alle Arten von Definitionen können auf der obersten Ebene geschrieben werden.
Paketobjekte werden nicht mehr benötigt, werden auslaufen.

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
quelle
Vielen Dank an @VonC, ich freue mich sehr auf Scala 3 aus diesem und vielen anderen Gründen. Ich habe Paketobjekte nicht viel genutzt, bin mir aber sicher, dass ich Definitionen der obersten Ebene verwenden werde.
Don Mackenzie