选项性是与数据类型无关的问题。所以,
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
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
的字段,其允许以下两种类型的值。
//
{ "type": "lov", "value": "somethingFromSomeFixedSet" },
//
{ "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程序员来说也很易懂和适用)。