So an Option[Int]
or Option[String]
or for that matter Option[A]
results in Some(A)
or None
, but a Boolean
is different as it inherently represents dual states (true
/false
), does it makes sense to have Option[Boolean]
? I face this frequently when a JSON
response should not include the boolean field based on certain business logic.
Any thoughts?
Optionality is an orthogonal concern to what type your data is. So yes, Option[Boolean]
makes just as much sense as say Option[Int]
.
Let's talk about your particular use case.
If the business rule says the field (say) isPrimeTime
has to be of type Boolean
, but is optional, then you should model it with an Option[Boolean]
.
None
in this context indicates absence of the value, Some(true)
would mean "present and true", Some(false)
would mean "present and false". This would also allow you to add defaults in your code like shown below:
val isPrimeTime = json.getAs[Option[Boolean]]("isPrimeTime").getOrElse(false)
You can employ various Option
combinators for other common use cases that arise in such settings. (I would let your imagination work on that.)
If your domain has a lot of these "tristate" fields, and if the third state indicates some domain-specific idea, something not evident from the context, I'd strongly advise creating your own sum type. Even though you can technically still use Option[Boolean]
, that may not be good for your sanity. Here is an example.
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
}
This would seem like this would be a huge wastage, since you're losing the combinators available on Option
. That's partly right. Partly because since the two types are isomorphic, writing bridges wouldn't be that hard.
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)
}
}
This is still significant duplication, and you have to judge on a case-to-case basis whether the added clarity makes up for it.
A similar advice was made by HaskellTips on twitter recently, and a similar discussion ensued. You can find it here.
Sometimes the presence or absence of a field is a decision you can encode into structure of your data. Using Option
would be wrong in such cases.
Here's an example. Imagine there is a field differentiator
for which the following two kind of values are allowed.
// #1
{ "type": "lov", "value": "somethingFromSomeFixedSet" },
// #2
{ "type": "text", "value": "foo", "translatable": false }
Here, assume the translatable
field is mandatory, but only for `"type": "text".
You could model it with Option
thus:
case class Differentiator(
_type: DifferentiatorType,
value: String,
translatable: Option[Boolean]
)
However a better way to model that would be:
sealed trait Differentiator
case class Lov(value: String) extends Differentiator
case class Text(value: String, translatable: Boolean) extends Differentiator
Yaron Minsky's Effective ML talk has a section on this. You may find it helpful. (Although he uses ML to illustrate the points, the talk is quite accessible and applicable to Scala programmers as well).
That is a correct use of Option[Boolean]
. Suppose I have a running job that will take some time to complete, and I want a value to represent whether the job was a success or not. That suggests a value of type Option[Boolean]
.
None
. Some(true)
or Some(false)
.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With