2011-03-10 9 views
14

Estoy intentando escribir una función de Scala que devuelve el valor predeterminado de un tipo (0, 0.0, falso, '\ 0', etc. para tipos de valores y nulo para tipos de referencia). Se me ocurrió esto:¿Cómo puedo obtener el valor predeterminado para un tipo en Scala?

def defaultValue[U]: U = { 
    class Default[U] { var default: U = _ } 
    new Default[U].default 
} 

y si bien esto funciona bien si es llamado directamente, devuelve NULL incluso para los tipos de valor cuando se invocan mediante una función que sí es genérico, como se muestra en esta sesión REPL:

Welcome to Scala version 2.8.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_24). 
Type in expressions to have them evaluated. 
Type :help for more information. 

scala> def defaultValue[U]: U = { class Default[U] {var default: U = _ }; new Default[U].default } 
defaultValue: [U]U 

scala> defaultValue[Boolean] // direct call works 
res0: Boolean = false 

scala> var res: Any = 0 
res: Any = 0 

scala> def setRes[U] = { res = defaultValue[U]; defaultValue[U] } 
setRes: [U]U 

scala> setRes[Boolean] // returns a Boolean, but... 
res1: Boolean = false 

scala> res 
res2: Any = null // ... sets the res variable to null. 

Puede alguien explicarme:

  1. por qué sucede esto (y por qué el compilador/intérprete no se queja si no hay suficiente información para que vuelva un verdadero booleano); y
  2. ¿cómo puedo solucionarlo?
+2

He encontrado una forma de hacerlo con ClassManifests y haciendo coincidir el borrado, pero no se ve muy bien. –

+1

¿Quién quiere apostar que el motivo no es borrado de tipo? De todos modos, dado que solo tiene un número finito de comportamientos deseados conocidos, ¿por qué no codificarlos? Feo, sí, pero probablemente sea más eficiente. – Raphael

Respuesta

4

Puede crear su propio Defaulttype-class para manejar esto. Aquí es cómo se ve el código. Agregué un manejo especial para las colecciones de scala que devuelve una colección vacía en lugar de nula.

import scala.collection.immutable 

class Default[+A](val default: A) 

trait LowerPriorityImplicits { 
    // Stop AnyRefs from clashing with AnyVals 
    implicit def defaultNull[A <: AnyRef]:Default[A] = new Default[A](null.asInstanceOf[A]) 
} 

object Default extends LowerPriorityImplicits { 
    implicit object DefaultDouble extends Default[Double](0.0) 
    implicit object DefaultFloat extends Default[Float](0.0F) 
    implicit object DefaultInt extends Default[Int](0) 
    implicit object DefaultLong extends Default[Long](0L) 
    implicit object DefaultShort extends Default[Short](0) 
    implicit object DefaultByte extends Default[Byte](0) 
    implicit object DefaultChar extends Default[Char]('\u0000') 
    implicit object DefaultBoolean extends Default[Boolean](false) 
    implicit object DefaultUnit extends Default[Unit](()) 

    implicit def defaultSeq[A]: Default[immutable.Seq[A]] = new Default[immutable.Seq[A]](immutable.Seq()) 
    implicit def defaultSet[A]: Default[Set[A]] = new Default[Set[A]](Set()) 
    implicit def defaultMap[A, B]: Default[Map[A, B]] = new Default[Map[A, B]](Map[A, B]()) 
    implicit def defaultOption[A]: Default[Option[A]] = new Default[Option[A]](None) 

    def value[A](implicit value: Default[A]): A = value.default 
} 

Estos son los resultados del uso de este en el repl. Observe que el valor predeterminado para String puede anularse creando un nuevo Default[String] implícito.

scala> Default.value[Int] 
res0: Int = 0 

scala> Default.value[Boolean] 
res1: Boolean = false 

scala> Default.value[String] 
res2: String = null 

scala> Default.value[Set[Int]] 
res3: Set[Int] = Set() 

scala> Default.value[immutable.Seq[Int]] 
res4: scala.collection.immutable.Seq[Int] = List() 

scala> Default.value[String] 
res5: String = null 

scala> Default.value[AnyRef] 
res6: AnyRef = null 

scala> implicit val emptyStringAsDefault:Default[String] = new Default[String]("") 
emptyStringAsDefault: Default[String] = [email protected] 

scala> Default.value[String] 
res7: String = "" 
8

Aquí es una versión más condensada de su problema:

scala> defaultValue[Boolean]: Any 
res0: Any = null 

scala> defaultValue[Boolean]: Boolean 
res1: Boolean = false 

La primera versión es lo que se aplica cuando se llama a res = defaultValue[U] porque a pesar de U es de tipo booleano, res es de tipo Cualquier

Si compila este pequeño programa con la opción -Xprint:all

object Test { 
    def defaultValue[U]: U = { class Default[U] {var default: U = _ }; new Default[U].default } 

    def main(args:Array[String]) { 
    val any = defaultValue[Boolean]: Any 
    println(any) 
    val bool = defaultValue[Boolean]: Boolean 
    println(bool) 
    } 
} 

Vas a ver que justo antes de la fase de borrado, que tienen:

val any: Any = (Test.this.defaultValue[Boolean](): Any); 
scala.this.Predef.println(any); 
val bool: Boolean = (Test.this.defaultValue[Boolean](): Boolean); 
scala.this.Predef.println(bool) 

Luego, al final de la fase de borrado:

val any: java.lang.Object = (Test.this.defaultValue(): java.lang.Object); 
scala.this.Predef.println(any); 
val bool: Boolean = (scala.Boolean.unbox(Test.this.defaultValue()): Boolean); 
scala.this.Predef.println(scala.Boolean.box(bool)) 

Lo que pasa es que bajo el capó defaultValue[Boolean] devuelve null en ambos casos, pero luego null se desempaqueta en falso cuando el tipo de devolución es booleano. Se puede comprobar que en el REPL:

scala> Boolean.unbox(null) 
res0: Boolean = false 

scala> null.asInstanceOf[Boolean] 
res1: Boolean = false 

Editar: Yo tenía una idea - no es que yo estoy recomendando. No está seguro de lo que su caso de uso es (res = false parece más fácil para mi ..)

scala> def f[@specialized U] = { class X { var x: U = _ }; (new X).x } 
f: [U]U 

scala> var res: Any = _ 
res: Any = null 

scala> def g[@specialized U] = { res = f[U]; f[U] } 
g: [U]U 

scala> g[Boolean] 
res0: Boolean = false 

scala> res 
res1: Any = false 
+0

Gracias, esa es una muy buena explicación para el por qué. Ahora, ¿por qué el compilador no puede decirme más en tiempo de compilación? Como, mira hombre, quieres un booleano, pero aquí puedo darte nulo, ¿perdón? –

+0

No estoy seguro de que haya otros casos de uso de 'def f [T]: T' que no sean los que tiene. Es difícil materializar una T sin ningún parámetro pasado. Entonces puede que no haya ninguna advertencia porque nadie pensó en hacer eso. – huynhjl

+0

El truco de @specialized es bueno, pero peligroso porque se rompe tan pronto como envuelve la llamada a f desde un contexto que no está especializado. Esto podría causar errores que son difíciles de rastrear. –

7

Para el registro, esto es la única que he encontrado (aún) para hacer este trabajo de forma fiable. Mejoras son bienvenidas.

def defaultValue[T: ClassManifest]: T = classManifest[T].erasure.toString match { 
    case "void" =>().asInstanceOf[T] 
    case "boolean" => false.asInstanceOf[T] 
    case "byte" => (0: Byte).asInstanceOf[T] 
    case "short" => (0: Short).asInstanceOf[T] 
    case "char" => '\0'.asInstanceOf[T] 
    case "int" => 0.asInstanceOf[T] 
    case "long" => 0L.asInstanceOf[T] 
    case "float" => 0.0F.asInstanceOf[T] 
    case "double" => 0.0.asInstanceOf[T] 
    case _ => null.asInstanceOf[T] 
} 

Soy consciente de que tengo nula, incluso si T <: NotNull, que es un problema. Por otra parte, hay un problema con la inicialización de vars con _ para las subclases NotNull.

5

Sé que ya hay "mejor respuesta", pero ¿y el muy simple:

def defaultValue[U: ClassManifest]: U = new Array[U](1)(0) 

parece que funciona, aunque es un poco caro debido a la creación de un objeto de matriz temporal. ¿Alguien sabe de algún caso en el que da el valor equivocado?

Estaba buscando una alternativa "más barata", pero esta pregunta me dice que probablemente no haya ninguna.

2

He escrito una publicación de blog sobre la creación de un mecanismo predeterminado para Scala. Puede encontrarlo here.

Si no desea Option[_] por defecto a None, String a "" etc, entonces deshacerse de los respectivos implícitos del objeto Default.

Cuestiones relacionadas