Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: Does Option[Boolean] makes sense?

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?

like image 667
iyerland Avatar asked Jun 17 '14 15:06

iyerland


2 Answers

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).

like image 196
missingfaktor Avatar answered Nov 09 '22 16:11

missingfaktor


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].

  • Until the job is done, I don't know whether it's successful or not, so it has value None.
  • When the job is done, it either was a success or it wasn't, so it has value Some(true) or Some(false).
like image 39
Timothy Shields Avatar answered Nov 09 '22 16:11

Timothy Shields