Objetos de paquete

92

¿Qué son los objetos de paquete, no tanto el concepto como su uso?

Intenté que funcionara un ejemplo y la única forma en que pude trabajar fue la siguiente:

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

Las observaciones que he hecho hasta ahora son:

package object _root_ { ... }

no está permitido (lo cual es razonable),

package object x.y { ... }

tampoco está permitido.

Parece que un objeto de paquete debe declararse en el paquete padre inmediato y, si se escribe como se indicó anteriormente, se requiere el formulario de declaración de paquete delimitado por llaves.

¿Son de uso común? ¿Si es así, cómo?

Don Mackenzie
fuente
1
@Brent, este es un gran recurso, no solo para el artículo del objeto del paquete. He oído hablar del autor, pero no me di cuenta de que había escrito esta gira de Scala, gracias.
Don Mackenzie

Respuestas:

128

Normalmente, pondría su objeto de paquete en un archivo separado llamado package.scala en el paquete al que corresponde. También puede utilizar la sintaxis del paquete anidado, pero eso es bastante inusual.

El caso de uso principal para los objetos de paquete es cuando necesita definiciones en varios lugares dentro de su paquete, así como fuera del paquete cuando usa la API definida por el paquete. Aquí hay un ejemplo:

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

}

Ahora las definiciones dentro de ese objeto de paquete están disponibles dentro de todo el paquete foo.bar. Además, las definiciones se importan cuando alguien ajeno a ese paquete importa foo.bar._.

De esta manera, puede evitar que el cliente API emita importaciones adicionales para usar su biblioteca de manera efectiva; por ejemplo, en scala-swing necesita escribir

import swing._
import Swing._

para tener todas las bondades onEDTy conversiones implícitas de Tuple2a Dimension.

Moritz
fuente
13
Advertencia: la sobrecarga de métodos no funciona en los objetos de paquete.
retrónimo
Me sorprende por qué se había elegido que el objeto del paquete debería definirse un nivel más arriba en la jerarquía del paquete. Por ejemplo, esto significa que debe contaminar el paquete virtual orgo de comnivel superior con su objeto de paquete si desea que pertenezca a su propio paquete raíz, por ejemplo org.foo. Descubrí que permitir que la definición esté directamente debajo del paquete del que debería ser parte, habría sido una interfaz de API de lenguaje un poco más adecuada.
matanster
58

Si bien la respuesta de Moritz es acertada, una cosa adicional a tener en cuenta es que los objetos de paquete son objetos. Entre otras cosas, esto significa que puede construirlos a partir de rasgos, usando herencia mixta. El ejemplo de Moritz podría escribirse como

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

}

Aquí el control de versiones es un rasgo abstracto, que dice que el objeto del paquete debe tener un método de "versión", mientras que JodaAliases y JavaAliases son rasgos concretos que contienen prácticos alias de tipo. Todos estos rasgos pueden ser reutilizados por muchos objetos de paquete diferentes.

Dave Griffith
fuente
Todo el tema se está abriendo mucho y parece estar acostumbrado a todo su potencial, gracias por otro rico ejemplo.
Don Mackenzie
1
pero no se pueden usar como vals, por lo que en realidad no son objetos
Eduardo Pareja Tobes
7

Podría hacer algo peor que ir directamente a la fuente. :)

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
fuente
@Alex Cruise, gracias, esto parece sugerir que necesitan una unidad de compilación separada (que quizás evita la restricción del paquete delimitado por llaves). El problema es que quiero algunos consejos de usuario sólidos en lugar de mi propia conjetura sobre cómo usarlos.
Don Mackenzie
5

El caso de uso principal para los objetos de paquete es cuando necesita definiciones en varios lugares dentro de su paquete, así como fuera del paquete cuando usa la API definida por el paquete.

No es así con Scala 3 , programado para ser lanzado a mediados de 2020, basado en Dotty , como aquí :

Definiciones de nivel superior

Todo tipo de definiciones se pueden escribir en el nivel superior.
Los objetos de paquete ya no son necesarios, se eliminarán gradualmente.

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
fuente
Gracias @VonC, tengo muchas ganas de Scala 3 por esta y muchas otras razones. No he hecho mucho uso de los objetos de paquete, pero estoy seguro de que usaré definiciones de nivel superior.
Don Mackenzie