函数是否可以返回不共享共同祖先的多种类型之一?

时间:2013-01-19 01:39:41

标签: scala

当然我意识到所有类型的都有一个共同的祖先,但我的意思是:

在动态类型语言中,通常的做法是使用“混合”返回类型。一种常见的情况是尝试从数据库中检索数据的函数,然后返回一个对象(使用找到的数据初始化)或FALSE(如果没有找到数据)。

一个小伪代码来证明这种反模式:

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

如果找到我的对象id的数据,我将DB记录作为对象返回。如果没有,我得到一个布尔值。然后,当然,我,客户端,处理多种可能的返回类型。

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

或者是否可以注释多种可能的返回类型?

(请注意,我不希望希望可以这样做,因为我希望强制执行函数返回类型尽可能明确。这对我来说是一种解脱要知道语言禁止含糊不清的返回类型,这就是我要求的原因。)

3 个答案:

答案 0 :(得分: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 Morris的Option Cheat Sheet,了解您可以在Option上调用的最常用方法的列表,以及它们如何转换为模式匹配。

另外两个替代方案是来自scalaz的ValidationTry,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上发布了很好的帖子。

答案 1 :(得分:18)

您要找的是标记的联合变体变体记录歧视联盟不相交的联合总和类型

结合产品类型,它们成为代数数据类型

Scala没有对代数数据类型的直接支持,但它不需要,因为它们可以通过继承轻松建模。 (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),并且作为{{1}存在于Scala的标准库中}。

但在您的特定情况下,有一种更具体的类型,称为scala.util.EitherMaybe,它封装了一个值可能存在或不存在的想法。它看起来像这样:

Option

此外,Scala已将此作为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。)

答案 2 :(得分: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)