2010-08-27 20 views
23

Tengo un código de Scala que hace bastante uso de genéricos, y he deducido de los documentos que usar un manifiesto en las restricciones de parametrización puede ayudarme a solucionar los problemas de borrado de tipo (por ejemplo, quiero crear una instancia de un nuevo objeto del tipo genérico). Solo que me gustaría saber más sobre cómo funciona esto. Casi se siente como un tipo de hashmap que está obteniendo una entrada para cada sitio de invocación ... ¿Puede alguien aquí dar más detalles?¿Cómo funciona el Manifiesto de Scala (2.8)?

class Image[T <: Pixel[T] : Manifest](fun() => T, size: Array[Int], data: Array[T]) { 
    def this(fun:() => T, size: Array[T]) { 
     this(fun, size, new Array[T](size(0) * size(1)); 
    } 
} 

Esto es algo que no parece estar cubierto de cualquiera de la documentación que he encontrado en el sitio, y en Google que en su mayoría conseguir las entradas más antiguas que tienen una sintaxis muy diferente, y desde 2.8 parece tener cambiaron muchas cosas, no estoy seguro de que sigan siendo precisas.

+0

posible duplicado de (http://stackoverflow.com/ questions/3213510/what-is-a-manifest-in-scala-and-when-do-you-need-it) –

+1

No estoy seguro de que esas respuestas realmente respondan cómo funciona, sin embargo? Por lo menos, podría ser más claro. ¿Es el punto que el compilador agrega un argumento adicional a los métodos/funciones para mantener la clase concreta para el parámetro genérico? ¿Y cuál es el operador <: <, de todos modos? – djc

+0

Eche un vistazo aquí: http://debasishg.blogspot.com/2010/08/using-generalized-type-constraints-how.html –

Respuesta

35

Ha sido un tiempo desde que abrí paso por el código fuente de Scala en un intento de responder a la misma pregunta ... pero la respuesta corta, por lo que recuerdo -

Manifiesto es un código de trucos para permitir que el compilador moverse por Tipo de borrado (no se usa en tiempo de ejecución). Hace que se generen varias rutas de código en tiempo de compilación para los posibles tipos de entrada que coincidan con el manifiesto.

El manifiesto se resuelve implícitamente, pero si hay alguna ambigüedad en tiempo de compilación sobre qué es el tipo manifiesto, el compilador se detendrá.

Con una copia de Manifest tiene algunas cosas disponibles. Los elementos principales que normalmente desea es o bien la java.lang.Class que fue borrado por medio de erasure:

class BoundedManifest[T <: Any : Manifest](value: T) { 
    val m = manifest[T] 
    m.erasure.toString match { 
    case "class java.lang.String" => println("String") 
    case "double" | "int" => println("Numeric value.") 
    case x => println("WTF is a '%s'?".format(x)) 
    } 
} 

class ImplicitManifest[T <: Any](value: T)(implicit m: Manifest[T]) { 
    m.erasure.toString match { 
    case "class java.lang.String" => println("String") 
    case "double" | "int" => println("Numeric value.") 
    case x => println("WTF is a '%s'?".format(x)) 
    } 
} 

new BoundedManifest("Foo Bar!") 
// String 
new BoundedManifest(5) 
// Numeric value. 
new BoundedManifest(5.2) 
// Numeric value. 
new BoundedManifest(BigDecimal("8.62234525")) 
// WTF is a 'class scala.math.BigDecimal'? 
new ImplicitManifest("Foo Bar!") 
// String 
new ImplicitManifest(5) 
// Numeric value. 
new ImplicitManifest(5.2) 
// Numeric value. 
new ImplicitManifest(BigDecimal("8.62234525")) 
// WTF is a 'class scala.math.BigDecimal'? 

Este es un ejemplo bastante poco firme, pero muestra lo que está pasando. Lo ejecuté también para la salida FWIW en Scala 2.8.

El límite [T ... : Manifest] es nuevo en Scala 2.8 ... solía tener que agarrar el manifiesto implícitamente como se muestra en ImplicitManifest. En realidad, NO OBTIENE una copia del Manifiesto. Pero puede buscar uno dentro de su código diciendo val m = manifest[T] ... manifest[_] se define en Predef y demostrablemente encontrará el tipo de manifiesto adecuado dentro de un bloque delimitado.

Los otros dos elementos principales que obtienes de Manifest es <:< y >:> que prueban el subtipo/supertipo de un manifiesto frente a otro. Si recuerdo correctamente, estas son MUY ingenuas en cuanto a la implementación y no siempre coinciden, pero tengo un montón de código de producción que las usa para probar contra algunas posibles entradas borradas.Un simple ejemplo de comprobación contra otro manifiesta:

class BoundedManifestCheck[T <: Any : Manifest](value: T) { 
    val m = manifest[T] 
    if (m <:< manifest[AnyVal]) { 
    println("AnyVal (primitive)") 
    } else if (m <:< manifest[AnyRef]) { 
    println("AnyRef") 
    } else { 
    println("Not sure what the base type of manifest '%s' is.".format(m.erasure)) 
    } 
} 


new BoundedManifestCheck("Foo Bar!") 
// AnyRef 
new BoundedManifestCheck(5) 
// AnyVal (primitive) 
new BoundedManifestCheck(5.2)  
// AnyVal (primitive) 
new BoundedManifestCheck(BigDecimal("8.62234525")) 
// AnyRef 

Jorge Ortiz tiene una gran entrada en el blog (aunque de edad) en esto: http://www.scala-blogs.org/2008/10/manifests-reified-types.html

EDITAR:

se puede ver en realidad lo Scala es haciendo pidiéndole que imprima los resultados de la fase del compilador de borrado.

Correr, en mi último ejemplo anterior scala -Xprint:erasure test.scala produce el siguiente resultado: [? ¿Qué es un manifiesto en Scala y cuando lo necesita]

final class Main extends java.lang.Object with ScalaObject { 
    def this(): object Main = { 
    Main.super.this(); 
    () 
    }; 
    def main(argv: Array[java.lang.String]): Unit = { 
    val args: Array[java.lang.String] = argv; 
    { 
     final class $anon extends java.lang.Object { 
     def this(): anonymous class $anon = { 
      $anon.super.this(); 
     () 
     }; 
     class BoundedManifestCheck extends java.lang.Object with ScalaObject { 
      <paramaccessor> private[this] val value: java.lang.Object = _; 
      implicit <paramaccessor> private[this] val evidence$1: scala.reflect.Manifest = _; 
      def this($outer: anonymous class $anon, value: java.lang.Object, evidence$1: scala.reflect.Manifest): BoundedManifestCheck = { 
      BoundedManifestCheck.super.this(); 
      () 
      }; 
      private[this] val m: scala.reflect.Manifest = scala.this.Predef.manifest(BoundedManifestCheck.this.evidence$1); 
      <stable> <accessor> def m(): scala.reflect.Manifest = BoundedManifestCheck.this.m; 
      if (BoundedManifestCheck.this.m().<:<(scala.this.Predef.manifest(reflect.this.Manifest.AnyVal()))) 
      scala.this.Predef.println("AnyVal (primitive)") 
      else 
      if (BoundedManifestCheck.this.m().<:<(scala.this.Predef.manifest(reflect.this.Manifest.Object()))) 
       scala.this.Predef.println("AnyRef") 
      else 
       scala.this.Predef.println(scala.this.Predef.augmentString("Not sure what the base type of manifest '%s' is.").format(scala.this.Predef.genericWrapArray(Array[java.lang.Object]{BoundedManifestCheck.this.m().erasure()}))); 
      protected <synthetic> <paramaccessor> val $outer: anonymous class $anon = _; 
      <synthetic> <stable> def Main$$anon$BoundedManifestCheck$$$outer(): anonymous class $anon = BoundedManifestCheck.this.$outer 
     }; 
     new BoundedManifestCheck($anon.this, "Foo Bar!", reflect.this.Manifest.classType(classOf[java.lang.String])); 
     new BoundedManifestCheck($anon.this, scala.Int.box(5), reflect.this.Manifest.Int()); 
     new BoundedManifestCheck($anon.this, scala.Double.box(5.2), reflect.this.Manifest.Double()); 
     new BoundedManifestCheck($anon.this, scala.package.BigDecimal().apply("8.62234525"), reflect.this.Manifest.classType(classOf[scala.math.BigDecimal])) 
     }; 
     { 
     new anonymous class $anon(); 
     () 
     } 
    } 
    } 
} 
+1

También es notable que la eliminación de ': Manifiesto' del ejemplo final causa una falla de compilación -' error: no se pudo encontrar el valor implícito para el parámetro m: Manifiesto [T] 'cuando se llama a' val m = manifiesto [T] '. El límite de contexto proporciona información que la llamada 'manifest [_]' necesita para operar, por lo que no es opcional. –

4

El "contexto vinculado" T ... : Manifest es azúcar sintáctica para un argumento implícito: (implicit man: Manifest[T]). Por lo tanto, en el momento de la instanciación del constructor de tipo especificado por class Image, el compilador encuentra/suministra el Manifiesto para el tipo real utilizado para el parámetro de tipo T y ese valor "se adhiere a" la instancia de clase resultante a lo largo de su existencia y "anclas" instancia particular de Image[Something] al Manifiesto por su T.

+0

Gracias, eso ayuda, aunque me interesaría saber cómo se mantiene el valor con la instancia, es decir, dónde se guarda. – djc

+0

Igual que cualquier parámetro de constructor (al que se hace referencia fuera del constructor): en un campo. –

+0

editó mi respuesta para incluir el resultado de la fase de borrado. De hecho, puede hacer que Scalac le muestre lo que está generando al pasar el argumento '-Xprint: borrado' al compilador/tiempo de ejecución. Esto imprime el estado de su código Scala después de que se ejecuta la fase de borrado. –

Cuestiones relacionadas