11

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

在动态类型语言中,具有“混合”返回类型是一种常见的做法。一个常见的情况是一个函数尝试从数据库中检索数据,然后返回一个对象(用找到的数据初始化)或 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
  }
}

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

(请注意,我不希望这样做,因为我希望强制函数返回类型尽可能明确。得知该语言禁止模棱两可的返回类型,这让我松了一口气,这就是我要问的原因。)

4

3 回答 3

22

是的,使用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以及它们如何转化为模式匹配。

另外两个替代方案Validation来自 scalaz 和TryScala 2.10 中的新功能。

因为ValidationStackOverflow 上有一些非常好的答案,例如:Scala 中的方法参数验证,带有 for comprehension 和 monads

Try参阅此博客文章:Scala 新手指南第 6 部分:使用 Try 进行错误处理Option同一位作者在和上有很好的帖子Either

于 2013-01-19T02:03:24.897 回答
21

您要查找的内容称为标记联合变体变体记录区分联合不相交联合总和类型

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

Scala 不直接支持代数数据类型,但它不需要,因为它们可以通过继承轻松建模。(Scala确实sealed支持封闭ADT 的修饰符。)

注意:从 Scala 3 开始,Scala确实以enums的形式直接支持代数和类型。以下所有内容仅适用于 Scala 2。

在您的示例中,如果您知道返回类型是SomeTypeor SomeOtherType,则可以这样建模:

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]

这实际上是一种众所周知的数据类型,称为 an Either(因为它包含 anA或 a B),并且在 Scala 的标准库中以scala.util.Either.

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

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 提供为scala.Option.

Eitherover的好处Option是它允许你在失败的情况下也返回信息,而不是只表明没有价值你也可以说为什么没有价值。(按照惯例,左边Either是错误,右边是“有用”的值。)

的优点Option是它是一个单子。(注意:你可以Either通过向左或向右偏置来创建一个 monad。)

于 2013-01-19T02:38:04.917 回答
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)
于 2013-01-19T02:02:37.093 回答