Scala Language
opsommingen
Zoeken…
Opmerkingen
Aanpak met sealed trait
en case objects
heeft de voorkeur omdat Scala-opsomming een paar problemen heeft:
- Tellingen hebben hetzelfde type na het wissen.
- Compiler klaagt niet over "Match is niet uitputtend", als geval wordt gemist zal het mislukken 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)
Vergelijken met:
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 {
^
Meer gedetailleerde uitleg wordt gepresenteerd in dit artikel over Scala-telling .
Dagen van de week met Scala-telling
Java-achtige opsommingen kunnen worden gemaakt door Enumeratie uit te breiden.
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
Het is ook mogelijk om een voor mensen leesbare naam toe te voegen voor waarden in een opsomming:
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
Pas op voor het niet-zo-typerende veilige gedrag, waarbij verschillende opsommingen als hetzelfde instantietype kunnen evalueren:
object Parity extends Enumeration {
val Even, Odd = Value
}
WeekDays.Mon.isInstanceOf[Parity.Value]
>> res1: Boolean = true
Gebruik van verzegelde eigenschap- en kastobjecten
Een alternatief voor het verlengen van de Enumeration
is het gebruik van sealed
case-objecten:
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
}
Het sealed
trefwoord garandeert dat de eigenschap WeekDay
niet in een ander bestand kan worden verlengd. Hierdoor kan de compiler bepaalde veronderstellingen maken, waaronder dat alle mogelijke waarden van WeekDay
al zijn opgesomd.
Een nadeel is dat u met deze methode geen lijst met alle mogelijke waarden kunt verkrijgen. Om een dergelijke lijst te krijgen, moet deze expliciet worden verstrekt:
val allWeekDays = Seq(Mon, Tue, Wed, Thu, Fri, Sun, Sat)
Gevalklassen kunnen ook een sealed
eigenschap uitbreiden. Objecten en casusklassen kunnen dus worden gemengd om complexe hiërarchieën te maken:
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
}
Een ander nadeel is dat er geen manier is om toegang te krijgen tot de variabelenaam van de opsomming van een sealed
object, of er naar te zoeken. Als u een soort naam nodig hebt die aan elke waarde is gekoppeld, moet deze handmatig worden gedefinieerd:
sealed trait WeekDay { val name: String }
object WeekDay {
case object Mon extends WeekDay { val name = "Monday" }
case object Tue extends WeekDay { val name = "Tuesday" }
(...)
}
Of gewoon:
sealed case class WeekDay(name: String)
object WeekDay {
object Mon extends WeekDay("Monday")
object Tue extends WeekDay("Tuesday")
(...)
}
Gebruik van verzegelde eigenschap- en caseobjecten en allValues-macro
Dit is slechts een uitbreiding op de verzegelde eigenschapsvariant waarbij een macro een set genereert met alle instanties tijdens het compileren. Hiermee wordt het nadeel dat een ontwikkelaar een waarde aan de opsomming kan toevoegen, netjes weggelaten, maar vergeet deze aan de set allElements toe te voegen.
Deze variant wordt vooral handig voor grote enums.
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]
}
Om dit te laten werken heb je deze macro nodig:
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(_))
)
}
}
}
}