Sök…


Anmärkningar

Tillvägagångssätt med sealed trait och case objects föredras eftersom Scala-uppräkning har några problem:

  1. Uppräkningar har samma typ efter radering.
  2. Compiler klagar inte om "Match är inte uttömmande", om fall saknas misslyckas det i 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)

Jämför med:

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 {
                                          ^

Mer detaljerad förklaring presenteras i denna artikel om Scala Enumeration .

Veckodagar med Scala Enumeration

Java-liknande uppräkningar kan skapas genom att utöka uppräkning .

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

Det är också möjligt att lägga till ett mänskligt läsbart namn för värden i en uppräkning:

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

Se upp för det icke-så-typiska beteendet, där olika uppräkningar kan utvärdera som samma förekomsttyp:

object Parity extends Enumeration {
   val Even, Odd = Value
}
  
WeekDays.Mon.isInstanceOf[Parity.Value]
>> res1: Boolean = true

Använd förseglade drag och fallföremål

Ett alternativ till att utvidga Enumeration är att använda sealed föremål:

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
}

Det sealed nyckelordet garanterar att egenskaperna WeekDay inte kan utökas i en annan fil. Detta gör att kompilatorn kan göra vissa antaganden, inklusive att alla möjliga värden för WeekDay redan är räknade.

En nackdel är att den här metoden inte tillåter dig att få en lista över alla möjliga värden. För att få en sådan lista måste den anges uttryckligen:

val allWeekDays = Seq(Mon, Tue, Wed, Thu, Fri, Sun, Sat)

Fallklasser kan också förlänga ett sealed drag. Således kan objekt och fallklasser blandas för att skapa komplexa hierarkier:

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
}

En annan nackdel är att det inte finns något sätt att få åtkomst till en variabelns namn på ett sealed objekts uppräkning eller söka efter det. Om du behöver ett slags namn kopplat till varje värde måste det definieras manuellt:

  sealed trait WeekDay { val name: String }

  object WeekDay {
      case object Mon extends WeekDay { val name = "Monday" }
      case object Tue extends WeekDay { val name = "Tuesday" }
      (...)   
  }

Eller bara:

  sealed case class WeekDay(name: String)
    
  object WeekDay {
      object Mon extends WeekDay("Monday")
      object Tue extends WeekDay("Tuesday")
      (...)   
  }

Använda förseglade drag och fallobjekt och allValues-macro

Detta är bara en förlängning på den förseglade dragvarianten där ett makro genererar en uppsättning med alla instanser vid sammanställningstiden. Detta utesluter fint nackdelen att en utvecklare kan lägga till ett värde i uppräkningen men glömmer att lägga till det i seten allElements.

Denna variant blir särskilt praktisk för stora 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]
}

För att detta ska fungera behöver du den här makroen:

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(_))
        )
      }
    }
  }
}


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow