Objets de package

92

Que sont les objets de package, pas tant le concept que leur utilisation?

J'ai essayé de faire fonctionner un exemple et le seul formulaire que j'ai pu travailler était le suivant:

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

Les observations que j'ai faites jusqu'à présent sont:

package object _root_ { ... }

est interdit (ce qui est raisonnable),

package object x.y { ... }

est également interdit.

Il semble qu'un objet package doit être déclaré dans le package parent immédiat et, s'il est écrit comme ci-dessus, le formulaire de déclaration de package délimité par des accolades est requis.

Sont-ils d'usage courant? Si c'est le cas, comment?

Don Mackenzie
la source
1
@Brent, c'est une excellente ressource, pas seulement pour l'article sur l'objet du package. J'ai entendu parler de l'auteur mais je ne savais pas qu'il avait écrit cette tournée Scala, merci.
Don Mackenzie

Réponses:

128

Normalement, vous placez votre objet package dans un fichier séparé appelé package.scaladans le package auquel il correspond. Vous pouvez également utiliser la syntaxe du package imbriqué, mais c'est assez inhabituel.

Le cas d'utilisation principal des objets de package est lorsque vous avez besoin de définitions à divers endroits à l'intérieur de votre package ainsi qu'à l'extérieur du package lorsque vous utilisez l'API définie par le package. Voici un exemple:

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

}

Maintenant, les définitions à l'intérieur de cet objet package sont disponibles dans tout le package foo.bar. De plus, les définitions sont importées lorsqu'une personne extérieure à ce package importe foo.bar._.

De cette façon, vous pouvez éviter d'exiger du client API qu'il émette des importations supplémentaires pour utiliser efficacement votre bibliothèque - par exemple, dans scala-swing, vous devez écrire

import swing._
import Swing._

pour avoir toute la bonté onEDTet les conversions implicites de Tuple2à Dimension.

Moritz
la source
13
Attention: la surcharge de méthode ne fonctionne pas dans les objets de package.
retronym
Cela me vaut la peine de savoir pourquoi il avait été choisi que l'objet package soit défini un niveau plus haut dans la hiérarchie des packages. Par exemple, cela signifie que vous devez polluer le package virtuel orgou de comniveau supérieur avec votre objet package si vous souhaitez qu'il appartienne à votre propre package racine, par exemple org.foo. Je trouve que permettre à la définition d'être directement sous le paquet dont elle devrait faire partie - aurait été une interface API de langue légèrement plus appropriée.
matanster
58

Bien que la réponse de Moritz soit parfaite, une chose supplémentaire à noter est que les objets de package sont des objets. Entre autres choses, cela signifie que vous pouvez les construire à partir de traits, en utilisant l'héritage mixte. L'exemple de Moritz pourrait être écrit comme

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

}

Ici, le versionnage est un trait abstrait, qui dit que l'objet package doit avoir une méthode "version", tandis que JodaAliases et JavaAliases sont des traits concrets contenant des alias de type pratiques. Tous ces traits peuvent être réutilisés par de nombreux objets de package différents.

Dave Griffith
la source
L'ensemble du sujet s'ouvre beaucoup et il semble être utilisé à son plein potentiel, merci pour un autre exemple riche.
Don Mackenzie
1
mais ils ne peuvent pas être utilisés comme vals, donc ce ne sont pas vraiment des objets
Eduardo Pareja Tobes
7

Vous pourriez faire pire que d'aller directement à la source. :)

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

Croisière Alex
la source
@Alex Cruise, merci, cela semble suggérer qu'ils ont besoin d'une unité de compilation séparée (qui contourne peut-être la restriction de paquet délimité par accolades). Le problème est que je veux des conseils d'utilisation solides plutôt que ma propre conjecture sur la façon de les utiliser.
Don Mackenzie
5

Le cas d'utilisation principal des objets de package est lorsque vous avez besoin de définitions à divers endroits à l'intérieur de votre package ainsi qu'à l'extérieur du package lorsque vous utilisez l'API définie par le package.

Ce n'est pas le cas avec Scala 3 , dont la sortie est prévue mi-2020, basé sur Dotty , comme ici :

Définitions de Toplevel

Toutes sortes de définitions peuvent être écrites au niveau supérieur.
Les objets de package ne sont plus nécessaires, seront progressivement supprimés.

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
la source
Merci @VonC, j'attends vraiment avec impatience Scala 3 pour cela et bien d'autres raisons. Je n'ai pas beaucoup utilisé les objets de package mais je suis sûr que j'utiliserai des définitions de premier niveau.
Don Mackenzie