Scala:Option[Boolean] 有意义吗?

10
因此,Option[Int], Option[String]或者其他类型的Option[A]将产生Some(A)None,但是Boolean不同,因为它本质上代表了双重状态(true/false),那么有Option[Boolean]有意义吗?在某些业务逻辑下,当JSON响应不应包含布尔字段时,我经常面临这个问题。
你有什么想法吗?

5
可以,这是三态的一种合理表示方式:真、假、不知道/不关心。 - Randall Schulz
1
也许不是最优的性能(因为它是一个完整的类),但为什么不呢? 如果需要优化,可以将其编码为Byte或Int。 - Display Name
2个回答

15
选项性是与数据类型无关的问题。所以,Option[Boolean]Option[Int] 同样有意义。
现在,让我们谈论您特定的使用情况。
如果业务规则规定字段(比如说)isPrimeTime 必须为Boolean类型,但是又是可选的,那么您应该使用 Option[Boolean] 进行建模。
在这种情况下,None 表示值的缺失,Some(true) 表示“存在并为真”, Some(false) 表示“存在并为假”。这也允许您在代码中添加默认值,如下所示:
val isPrimeTime = json.getAs[Option[Boolean]]("isPrimeTime").getOrElse(false)

你可以使用各种Option组合器来处理在这些情境下出现的其他常见用例。(我会让你发挥想象力。)
如果你的领域有很多这样的“三值”字段,并且第三个状态表示某个特定于领域的想法,而不是从上下文中显而易见的东西,我强烈建议创建自己的sum类型。即使你在技术上仍然可以使用Option[Boolean],但那可能不利于你的理智。以下是一个例子。
sealed trait SignalValueIndication
case class Specified(value: Boolean) extends SignalValueIndication
case object DontCare extends SignalValueIndication

// Use
val signalValue = signalValueIndication match {
  case Specified(value) => value
  case DontCare => chooseOptimalSignalValueForImpl
}

这似乎是很大的浪费,因为你失去了Option上可用的组合器。这部分是正确的。部分是因为由于这两种类型是同构的,编写桥梁并不那么困难。
sealed trait SignalValueIndication {
  // ...
  def toOption: Option[Boolean] = this match {
    case Specified(value) => Some(value)
    case DontCare => None
  }

  def getOrElse(fallback: => Boolean) = this.toOption.getOrElse(fallback)
}

object SignalValueIndication {
  def fromOption(value: Option[Boolean]): SignalValueIndication = {
    value.map(Specified).getOrElse(DontCare)
  }
}

这仍然存在重复,你需要以情况为基础进行判断,是否增加了清晰度。

最近HaskellTips在twitter上也给出了类似的建议,并引发了一场讨论。在这里可以找到链接


有时候一个字段的存在或缺失是你可以编码到数据结构中的决策。在这种情况下,使用Option是不正确的。

这是一个例子。假设存在一个名为differentiator的字段,其允许以下两种类型的值。

// #1
{ "type": "lov", "value": "somethingFromSomeFixedSet" },

// #2
{ "type": "text", "value": "foo", "translatable": false }

假设在这里,“translatable”字段是必填的,但仅适用于“type”为“text”的情况。您可以使用“Option”对其进行建模,如下所示:
假设在这里,“translatable”字段是必填的,但仅适用于“type”为“text”的情况。您可以使用“Option”对其进行建模,如下所示:
case class Differentiator(
  _type: DifferentiatorType, 
  value: String, 
  translatable: Option[Boolean]
)

然而,更好的建模方式应该是:

sealed trait Differentiator
case class Lov(value: String) extends Differentiator
case class Text(value: String, translatable: Boolean) extends Differentiator

Yaron Minsky的Effective ML演讲涉及到了这个问题。你可能会发现它很有帮助。(虽然他使用了机器学习来说明这些观点,但演讲对Scala程序员来说也很易懂和适用)。


11

这是使用 Option[Boolean] 的正确方式。假设我有一个正在运行的任务需要一些时间才能完成,并且我想要一个值来表示该任务是否成功。这暗示了一种类型为 Option[Boolean] 的值。

  • 在任务完成之前,我不知道它是否成功,因此它的值为 None
  • 当任务完成时,它要么成功了,要么失败了,所以它的值为 Some(true)Some(false)

如果您能提供一个Option[Option[Boolean]]的示例,那就太棒了。 - Timothy Shields
1
对于任何 X,您可能希望使用 Option[X] 来指示 X 是否可用。例如,假设您有一个子任务来检查作业是否成功;该子任务报告一个 Option[Boolean] 来指示成功/失败/不完整,并且您可能会返回一个 Option[Option[Boolean]] 来指示子任务未响应。最终,您可能会将其展平,但最初您希望对任务失败和任务处理程序失败做出非常不同的响应。 - Rex Kerr
@RexKerr 我的意思是想让它变得幽默。 :) 但无论如何,这是一个好的解释。 - Timothy Shields

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接