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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14410275/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 04:51:15  来源:igfitidea点击:

Can a function return one of multiple types which do not share a common ancestor?

scala

提问by Ben Wilhelm

Of course I realize all types dohave a common ancestor, but what I mean is this:

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

In dynamically-typed languages, it is a common practice to have 'mixed' return types. A common case is a function which attempts to retrieve data from a database, then returns either an object (initialized with the found data) or FALSE (in the event no data was found).

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

A little pseudocode to demonstrate just such an anti-pattern:

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

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

If data is found for my object id, I get a DB record back as an object. If not, I get a boolean value. Then, of course, it is on me, the client, to handle multiple possible return types.

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

Is the only way to accomplish this in Scala to find a common ancestor for all possible return types and declare that as the return type in the signature?

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

Or is it possible to annotate multiple possible return types?

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

(Note that I do not hopeit is possible to do this, as I would prefer it to be enforced that function return types are as unambiguous as possible. It would come as a relief to me to learn that the language forbids ambiguous return types, which is more the reason I am asking.)

(请注意,我不希望这样做是可能的,因为我希望强制执行函数返回类型尽可能明确。了解到该语言禁止不明确的返回类型对我来说是一种解脱,这更像是我问的原因。)

回答by sourcedelica

Yes, use Either:

是的,使用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
}

Or, if the no results case doesn't need a specific value, use Option:

或者,如果没有结果的情况不需要特定值,请使用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
}

See Tony Morris' Option Cheat Sheetfor a list of the most common methods you can call on Optionand how they translate to pattern matching.

请参阅 Tony Morris 的Option Cheat Sheet,了解您可以调用的最常用方法的列表Option以及它们如何转换为模式匹配。

Two other alternatives are Validationfrom scalaz and Try, new in Scala 2.10.

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

For Validationthere are some really good answers on StackOverflow, for example: Method parameters validation in Scala, with for comprehension and monads.

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

For Trysee this blog post: The Neophyte's Guide to Scala Part 6: Error Handling With Try. The same author has good posts on Optionand Either.

对于Try看到这篇博客文章:新信徒的Scala指南第6部分:错误处理与尝试。同一作者在Option和上有很好的帖子Either

回答by J?rg W Mittag

What you are looking for is called a tagged union, variant, variant record, discriminated union, disjoint union, or sum type.

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

Combined with product types, they become algebraic datatypes.

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

Scala does not have direct support for algebraic datatypes, but it doesn't need to, because they can be easily modeled by inheritance. (Scala doeshave the sealedmodifier to support closedADTs.)

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

In your example, if you know that the return type is either SomeTypeor SomeOtherType, you can model it like this:

在你的例子中,如果你知道返回类型是SomeTypeor SomeOtherType,你可以像这样建模:

sealed trait ReturnType

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

def meth: ReturnType

If you don't know what the return types are, only that there are two of them, then you can model it similarly:

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

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]

This is actually a well-known datatype called an Either(because it holds either an Aor a B), and is present in Scala's standard library as scala.util.Either.

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

But in your specific case, there is a more specific type, called Maybeor Option, which encapsulates the idea that a value maybe exists or maybe not. It looks something like this:

但是在您的特定情况下,有一种更具体的类型,称为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]

Again, this is already provided by Scala as scala.Option.

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

The advantage of Eitherover Optionis that it allows you to also return information in the failure case instead of only indicating that there is no value you can also say whythere is no value. (By convention, the left side of the Eitheris the error, the right side is the "useful" value.)

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

The advantage of Optionis that it is a monad. (Note: you can make Eithera monad by biasing it either to the left or the right.)

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

回答by 7zark7

If you will you know at runtime what type you are querying for in each call, your signature could be as:

如果您在运行时知道您在每次调用中查询的类型,您的签名可以是:

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

Or, to simulate your if/else logic there, I would recommend using Option here:

或者,为了在那里模拟您的 if/else 逻辑,我建议在此处使用 Option:

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

Example usage:

用法示例:

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