可选性是与您的数据类型相关的正交问题。所以是的,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 上提出了类似的建议,随后进行了类似的讨论。你可以找到它here。
有时,是否存在字段是您可以将其编码到数据结构中的决定。在这种情况下使用Option 是错误的。
这是一个例子。假设有一个字段differentiator 允许以下两种值。
// #1
{ "type": "lov", "value": "somethingFromSomeFixedSet" },
// #2
{ "type": "text", "value": "foo", "translatable": false }
在这里,假设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 谈话中有一个部分对此进行了介绍。您可能会发现它很有帮助。 (虽然他使用 ML 来说明这些观点,但演讲很容易理解,也适用于 Scala 程序员)。