2013-01-19 26 views
8

當然我知道所有類型有一個共同的祖先,但我的意思是這樣的:函數是否可以返回不共享祖先的多種類型之一?

在動態類型語言,它是一種常見的做法有「混合」的返回類型。一個常見的情況是嘗試從數據庫檢索數據,然後返回一個對象(使用找到的數據進行初始化)或FALSE(在沒有找到數據的情況下)的函數。

有點僞證明了這樣一個反模式:

function getObjectFromDatabase(object_id) { 
    if(result = db_fetch_object("SELECT * FROM objects WHERE id = %d", object_id) { 
    return result 
    } else { 
    return FALSE 
    } 
} 

如果找到我的對象ID數據,我得到一個數據庫記錄追溯到一個對象。如果沒有,我得到一個布爾值。然後,當然,這是我的客戶,處理多種可能的返回類型。

在Scala中找到所有可能的返回類型的共同祖先並將其聲明爲簽名中的返回類型是唯一的方法嗎?

// Like so: 
def getObjectFromDatabase(objectId: Int): Any = { 
    val result = dbFetchObject("SELECT * FROM objects WHERE id = %d", object_id) 
    if(result) { 
    return result 
    } else { 
    return false 
    } 
} 

或者是有可能註釋多個可能的返回類型? (請注意,我不想希望有可能做到這一點,因爲我希望它被強制執行,函數返回類型儘可能明確。這將使我瞭解到。語言禁止模棱兩可的返回類型,這是比較我要求的原因)

回答

16

什麼你正在尋找被稱爲標籤聯合變種變體記錄識別聯合不交,或和類型

結合產品類型,它們變成代數數據類型

Scala沒有直接支持代數數據類型,但它不需要,因爲它們可以通過繼承輕鬆建模。 (斯卡拉確實sealed修改以支持關閉的ADT)

在您的例子,如果你知道,返回類型爲SomeTypeSomeOtherType,你可以喜歡這個型號:

sealed trait ReturnType 

final case class SomeType extends ReturnType 
final case class SomeOtherType extends ReturnType 

def meth: ReturnType 

如果你不知道的返回類型是什麼,只是有兩個人,那麼你同樣可以模擬它:

sealed trait ReturnType[A, B] 

final case class Type1[A, B](a: A) extends ReturnType[A, B] 
final case class Type2[A, B](b: B) extends ReturnType[A, B] 

def meth: ReturnType[A, B] 

這實際上是一個衆所周知的數據類型,稱爲Either(因爲它包含AB),並且在斯卡拉的標準庫中存在爲scala.util.Either

但在特定情況下,有一個更具體的類型,稱爲MaybeOption,它封裝了一個價值也許存在,或者也許不是想法。它看起來是這樣的:

sealed trait Maybe[T] 

case object None extends Maybe[Nothing] 
final case class Just[T](value: T) extends Maybe[T] 

def meth: Maybe[T] 

再次,這是已經被斯卡拉作爲scala.Option

Either超過Option的好處是,它可以讓你也回在故障情況下的信息,而不是隻表明有沒有價值,你也可以說爲什麼沒有價值。 (按照慣例,Either的左側是錯誤,右側是「有用」值。)

Option的優點是它是一個monad。 (注意:通過向左或向右偏移它可以使Either成爲monad)。

2

如果你就會知道在運行時是什麼類型你在每次調用查詢,您的簽名可能是因爲:

def getObjectFromDatabase[T](object_id: Int): T = { 

或者,爲了模擬你的if/else邏輯,我建議在這裏使用Option:

def getObjectFromDatabase[T](object_id: Int): Option[T] = { 
    ... 
    if(result) Some(result) 
    else None 
} 

用法示例:

val result = getObjectFromDatabase[String](123123).getOrElse(whatever_you_need) 
20

是,使用Either

def getObjectFromDatabase(objectId: Int): Either[Boolean, DbResult] = { 
    val result = dbFetchObject("SELECT * FROM objects WHERE id = %d", object_id) 
    if (result) Right(result) else Left(false) 

} 

getObjectFromDatabase(id) match { 
    case Right(result) => // do something with result 
    case Left(bool) => // do something with bool 
} 

或者,如果沒有結果的情況下並不需要一個特定的值,用Option

def getObjectFromDatabase(objectId: Int): Option[DbResult] = { 
    val result = dbFetchObject("SELECT * FROM objects WHERE id = %d", object_id) 
    if (result) Some(result) else None 
} 

getObjectFromDatabase(id) match { 
    case Some(result) => // do something with result 
    case None => // do something about no results 
} 

查看Tony Morr是'Option Cheat Sheet的列表,您可以通過Option調用最常用的方法,以及它們如何轉換爲模式匹配。

另外兩個替代方案是scalaz的Validation和,Scala 2.10中的新方法。

對於Validation在StackOverflow上有一些非常好的答案,例如:Method parameters validation in Scala, with for comprehension and monads

對於Try看到這篇博文:The Neophyte's Guide to Scala Part 6: Error Handling With Try。同一作者在OptionEither上有很好的帖子。

+0

只有當預期的返回類型在邏輯上只能是這兩種類型中的一種時,「任一」纔是有效的選擇。從概念上講,這不是一個好的邏輯解決方案,你以後可以添加另一種類型。即不存在'Either [Int,String,Double]'這樣的事情 – Adrian

相關問題