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

Ben*_*elm 10 scala

当然,我知道所有类型有一个共同的祖先,但我的意思是这样的:

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

一个小伪代码来演示这样的反模式:

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

如果找到我的对象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
  }
}
Run Code Online (Sandbox Code Playgroud)

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

(请注意,我不希望有可能这样做,因为我希望强制执行函数返回类型尽可能明确.对我来说,学习语言禁止模糊返回类型会让我感到宽慰,这就是我要问的原因.)

sou*_*ica 21

是的,使用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
}
Run Code Online (Sandbox Code Playgroud)

或者,如果无结果案例不需要特定值,请使用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
}
Run Code Online (Sandbox Code Playgroud)

请参阅Tony Morris的选项备忘单,了解可以调用的最常用方法列表Option以及它们如何转换为模式匹配.

另外两个选择Validation来自scalaz和TryScala 2.10中的新选项.

因为ValidationStackOverflow上有一些非常好的答案,例如:Scala中的方法参数验证,用于理解和monad.

有关Try此博客文章的信息,请参阅新手的Scala指南第6部分:使用Try进行错误处理.同一位作者有很好的帖子OptionEither.


Jör*_*tag 18

您正在寻找的是称为标记联合,变体,变体记录,区分联合,不相交联合总和类型.

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

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

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

sealed trait ReturnType

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

def meth: ReturnType
Run Code Online (Sandbox Code Playgroud)

如果您不知道返回类型是什么,只有其中有两个,那么您可以类似地对其进行建模:

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]
Run Code Online (Sandbox Code Playgroud)

这实际上是一个众所周知的数据类型,称为Either(因为它包含一个A或一个B),并且在Scala的标准库中存在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]
Run Code Online (Sandbox Code Playgroud)

同样,这已经由Scala提供了scala.Option.

Either超过的优点Option是它允许您在故障情况下返回信息,而不是仅指示没有值,您也可以说为什么没有值.(按照惯例,左侧Either是错误,右侧是"有用"值.)

优点Option是它是一个monad.(注意:您可以Either通过向左或向右偏置来制作monad.)