2011-03-10 76 views
14

我想寫一個Scala函數,返回一個類型的默認值(0,0.0,假,'\ 0'等值類型和null爲參考類型)。我想出了這個:如何獲得Scala中某個類型的默認值?

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

,雖然這工作得很好,如果直接調用,它甚至在通過函數本身是通用的所謂價值型返回null,如本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. 

有人能向我解釋:

  1. 爲什麼發生這種情況(以及爲什麼如果沒有足夠的信息,它返回一個真正的布爾編譯器/解釋器不會抱怨);和
  2. 我該如何解決它?
+2

我發現一種方法可以用ClassManifests和匹配擦除,但它看起來不漂亮。 – 2011-03-10 14:33:24

+1

誰打賭的原因是不是類型擦除?無論如何,既然你只有許多已知的期望行爲,爲什麼不硬編碼呢?醜陋,是的,但可能是最有效的。 – Raphael 2011-03-10 19:10:30

回答

4

您可以創建自己的Defaulttype-class來處理此問題。這是代碼的樣子。我爲scala集合添加了特殊處理,它返回一個空集合而不是null。

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 
} 

這些是在repl中使用它的結果。請注意,String的默認值可以通過創建新的隱含Default[String]來重寫。

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

這是你的問題更濃縮版本:

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

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

第一個版本是,當你調用res = defaultValue[U]因爲即使U是布爾類型是什麼應用,res類型的任何

如果使用-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) 
    } 
} 

你會看到擦除階段之前這項權利,你必須:

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) 

然後,在擦除階段結束:

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

所以會發生什麼是引擎蓋下defaultValue[Boolean]在這兩種情況下都返回null,但是當返回類型是布爾值時,null將被拆箱爲假。你可以驗證在REPL:

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

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

編輯:我有一個想法 - 不是我推薦它。不知道你的使用情況是什麼(res = false似乎更容易我..)

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

謝謝,這是爲什麼的一個非常好的解釋。現在爲什麼編譯器不能告訴我更多?喜歡,看看男人,你想要一個布爾值,但在這裏我可能會給你null,對不起? – 2011-03-11 10:19:41

+0

不確定除了你擁有的'def f [T]:T'還有其他用例。沒有任何傳遞參數很難實現T。所以可能沒有警告,因爲沒有人想過這樣做。 – huynhjl 2011-03-12 04:14:03

+0

@specialized技巧很好,但很危險,因爲只要您將來自未專門化的上下文的調用包裝爲f,它就會崩潰。這可能會導致難以追查的錯誤。 – 2011-03-14 09:46:36

7

爲了記錄在案,這裏是唯一的,我發現(還),使這項工作可靠。歡迎改進。

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

我知道,我得到空就算T <: NotNull,這是一個問題。然後再次,對於NotNull子類,使用_進行變量初始化時出現問題。

5

我知道已經有「最佳答案」,但對於非常簡單:

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

它似乎工作,雖然這是由於創建一個臨時數組對象比較昂貴。有誰知道任何情況下它給出了錯誤的價值?

我正在尋找一個「便宜」的替代方案,但這個問題告訴我可能沒有一個。

2

我寫了一篇關於構建Scala缺省機制的博客文章。你可以找到它here

如果你不想Option[_]默認爲NoneString""等則得到來自物體Default擺脫各自implicits的。