L'opzione è una preoccupazione ortogonale per il tipo di dati. Quindi sì, Option[Boolean]
ha esattamente lo stesso significato di Option[Int]
.
Parliamo del tuo caso d'uso particolare.
Se la regola di business dice il campo (diciamo) isPrimeTime
deve essere di tipo Boolean
, ma è opzionale, allora si dovrebbe modellare con un Option[Boolean]
.
None
in questo contesto indica assenza del valore, Some(true)
significherebbe "presenti e vero", Some(false)
significherebbe "presente e falso". Ciò consentirebbe anche di aggiungere le impostazioni predefinite nel codice come illustrato di seguito:
val isPrimeTime = json.getAs[Option[Boolean]]("isPrimeTime").getOrElse(false)
si possono impiegare vari Option
combinatori per altri casi di uso comune che si presentano in tali impostazioni. (Avrei lasciate che il vostro lavoro immaginazione su quello.)
Se il dominio ha un sacco di questi campi "tre stati", e se il terzo stato indica alcuni specifici domini idea, qualcosa che non evidente dal contesto, Consiglio vivamente di creare il proprio tipo di somma. Anche se tecnicamente puoi ancora usare Option[Boolean]
, questo potrebbe non essere buono per la tua sanità mentale. Ecco un esempio.
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
}
Questo sembrerebbe come questo sarebbe un enorme spreco, dal momento che si sta perdendo i combinatori disponibili su Option
. Questo è in parte giusto. In parte perché poiché i due tipi sono isomorfi, scrivere ponti non sarebbe così difficile.
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)
}
}
Questo è ancora significativa la duplicazione, e si deve giudicare caso per caso se la chiarezza aggiunto costituisce per essa.
Un consiglio simile è stato fatto da HaskellTips su twitter di recente, e ne è seguita una discussione simile. Lo puoi trovare here.
A volte la presenza o l'assenza di un campo è una decisione che è possibile codificare nella struttura dei dati. Usare Option
sarebbe sbagliato in questi casi.
Ecco un esempio. Immagina che ci sia un campo differentiator
per il quale sono consentiti i seguenti due tipi di valori.
// #1
{ "type": "lov", "value": "somethingFromSomeFixedSet" },
// #2
{ "type": "text", "value": "foo", "translatable": false }
Qui, assumere il campo translatable
è obbligatorio, ma solo per ` "tipo": "text".
Si potrebbe modellare con Option
così:
case class Differentiator(
_type: DifferentiatorType,
value: String,
translatable: Option[Boolean]
)
Tuttavia un modo migliore per modello che potrebbe essere: Effective ML discorso di
sealed trait Differentiator
case class Lov(value: String) extends Differentiator
case class Text(value: String, translatable: Boolean) extends Differentiator
Yaron Minsky ha una sezione su questo. Potresti trovarlo utile. (Anche se usa ML per illustrare i punti, il discorso è abbastanza accessibile e applicabile anche ai programmatori di Scala).
Certo, è una rappresentazione ragionevole per un tri-stato: True, False, Don't-Know/Don't-Care. –
forse non prestazioni ottimali (perché è una classe in piena regola), ma perché no? se è necessario ottimizzare, è possibile codificarlo come Byte o Int, ad esempio –