Scala Language
enumerazioni
Ricerca…
Osservazioni
L'approccio con sealed trait
e case objects
è preferito perché l'enumerazione di Scala ha alcuni problemi:
- Le enumerazioni hanno lo stesso tipo dopo la cancellazione.
- Il compilatore non si lamenta del fatto che "Match non è esaustivo", se il caso è mancato fallirà in runtime
scala.MatchError
:
def isWeekendWithBug(day: WeekDays.Value): Boolean = day match {
case WeekDays.Sun | WeekDays.Sat => true
}
isWeekendWithBug(WeekDays.Fri)
scala.MatchError: Fri (of class scala.Enumeration$Val)
Confrontare con:
def isWeekendWithBug(day: WeekDay): Boolean = day match {
case WeekDay.Sun | WeekDay.Sat => true
}
Warning: match may not be exhaustive.
It would fail on the following inputs: Fri, Mon, Thu, Tue, Wed
def isWeekendWithBug(day: WeekDay): Boolean = day match {
^
Una spiegazione più dettagliata è presentata in questo articolo su Enumerazione di Scala .
Giorni della settimana usando Enumerazione Scala
Le enumerazioni Java-like possono essere create estendendo Enumeration .
object WeekDays extends Enumeration {
val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
}
def isWeekend(day: WeekDays.Value): Boolean = day match {
case WeekDays.Sat | WeekDays.Sun => true
case _ => false
}
isWeekend(WeekDays.Sun)
res0: Boolean = true
È anche possibile aggiungere un nome leggibile dall'uomo per i valori in un'enumerazione:
object WeekDays extends Enumeration {
val Mon = Value("Monday")
val Tue = Value("Tuesday")
val Wed = Value("Wednesday")
val Thu = Value("Thursday")
val Fri = Value("Friday")
val Sat = Value("Saturday")
val Sun = Value("Sunday")
}
println(WeekDays.Mon)
>> Monday
WeekDays.withName("Monday") == WeekDays.Mon
>> res0: Boolean = true
Fai attenzione al comportamento non tipicamente errato, in cui diverse enumerazioni possono valutare come lo stesso tipo di istanza:
object Parity extends Enumeration {
val Even, Odd = Value
}
WeekDays.Mon.isInstanceOf[Parity.Value]
>> res1: Boolean = true
Utilizzo di tratti sigillati e oggetti del caso
Un'alternativa all'estensione Enumeration
sta utilizzando oggetti caso sealed
:
sealed trait WeekDay
object WeekDay {
case object Mon extends WeekDay
case object Tue extends WeekDay
case object Wed extends WeekDay
case object Thu extends WeekDay
case object Fri extends WeekDay
case object Sun extends WeekDay
case object Sat extends WeekDay
}
La parola chiave sealed
garantisce che il tratto WeekDay
non possa essere esteso in un altro file. Ciò consente al compilatore di fare alcune ipotesi, incluso che tutti i valori possibili di WeekDay
sono già elencati.
Uno svantaggio è che questo metodo non consente di ottenere un elenco di tutti i valori possibili. Per ottenere una tale lista deve essere fornita esplicitamente:
val allWeekDays = Seq(Mon, Tue, Wed, Thu, Fri, Sun, Sat)
Le classi di casi possono anche estendere un tratto sealed
. Pertanto, oggetti e case case possono essere mescolati per creare gerarchie complesse:
sealed trait CelestialBody
object CelestialBody {
case object Earth extends CelestialBody
case object Sun extends CelestialBody
case object Moon extends CelestialBody
case class Asteroid(name: String) extends CelestialBody
}
Un altro svantaggio è che non c'è modo di accedere a un nome di variabile dell'enumerazione di un oggetto sealed
, o cercare da esso. Se è necessario un tipo di nome associato a ciascun valore, deve essere definito manualmente:
sealed trait WeekDay { val name: String }
object WeekDay {
case object Mon extends WeekDay { val name = "Monday" }
case object Tue extends WeekDay { val name = "Tuesday" }
(...)
}
O semplicemente:
sealed case class WeekDay(name: String)
object WeekDay {
object Mon extends WeekDay("Monday")
object Tue extends WeekDay("Tuesday")
(...)
}
Utilizzo di tratti tratti e casi e di tutti i valori-macro
Questa è solo un'estensione della variante del tratto sigillato in cui una macro genera un set con tutte le istanze in fase di compilazione. Ciò omette lo svantaggio che uno sviluppatore può aggiungere un valore all'enumerazione ma dimentica di aggiungerlo al set allElements.
Questa variante diventa particolarmente utile per grandi enumerazioni.
import EnumerationMacros._
sealed trait WeekDay
object WeekDay {
case object Mon extends WeekDay
case object Tue extends WeekDay
case object Wed extends WeekDay
case object Thu extends WeekDay
case object Fri extends WeekDay
case object Sun extends WeekDay
case object Sat extends WeekDay
val allWeekDays: Set[WeekDay] = sealedInstancesOf[WeekDay]
}
Per far funzionare questo è necessario questa macro:
import scala.collection.immutable.TreeSet
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
/**
A macro to produce a TreeSet of all instances of a sealed trait.
Based on Travis Brown's work:
http://stackoverflow.com/questions/13671734/iteration-over-a-sealed-trait-in-scala
CAREFUL: !!! MUST be used at END OF code block containing the instances !!!
*/
object EnumerationMacros {
def sealedInstancesOf[A]: TreeSet[A] = macro sealedInstancesOf_impl[A]
def sealedInstancesOf_impl[A: c.WeakTypeTag](c: blackbox.Context) = {
import c.universe._
val symbol = weakTypeOf[A].typeSymbol.asClass
if (!symbol.isClass || !symbol.isSealed)
c.abort(c.enclosingPosition, "Can only enumerate values of a sealed trait or class.")
else {
val children = symbol.knownDirectSubclasses.toList
if (!children.forall(_.isModuleClass)) c.abort(c.enclosingPosition, "All children must be objects.")
else c.Expr[TreeSet[A]] {
def sourceModuleRef(sym: Symbol) = Ident(sym.asInstanceOf[scala.reflect.internal.Symbols#Symbol
].sourceModule.asInstanceOf[Symbol]
)
Apply(
Select(
reify(TreeSet).tree,
TermName("apply")
),
children.map(sourceModuleRef(_))
)
}
}
}
}