Szukaj…


Uwagi

Preferowane jest podejście z sealed trait i case objects ponieważ wyliczenie Scala ma kilka problemów:

  1. Wyliczenia mają ten sam typ po usunięciu.
  2. Kompilator nie narzeka na „Dopasowanie nie jest wyczerpujące”, jeśli pominięto scala.MatchError nie powiedzie się w scala.MatchError środowiska wykonawczego.
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)

Porównać z:

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

Bardziej szczegółowe wyjaśnienie znajduje się w tym artykule na temat wyliczania Scala .

Dni tygodnia za pomocą Scala Enumeration

Wyliczenia podobne do Java można tworzyć, rozszerzając Wyliczanie .

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

Możliwe jest również dodanie czytelnej dla człowieka nazwy wartości w wyliczeniu:

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

Uważaj na niezbyt bezpieczne zachowanie, w którym różne wyliczenia mogą być oceniane jako ten sam typ wystąpienia:

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

Używanie zapieczętowanych obiektów cech i przypadków

Alternatywą dla rozszerzenia Enumeration jest użycie obiektów sealed skrzynek:

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
}

sealed słowo kluczowe gwarantuje, że cechy WeekDay nie można rozszerzyć w innym pliku. Pozwala to kompilatorowi na przyjęcie pewnych założeń, w tym, że wszystkie możliwe wartości WeekDay są już wyliczone.

Jedną wadą jest to, że ta metoda nie pozwala uzyskać listy wszystkich możliwych wartości. Aby uzyskać taką listę, należy ją wyraźnie podać:

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

Klasy przypadków mogą również rozszerzać sealed cechę. W ten sposób można łączyć obiekty i klasy spraw, aby tworzyć złożone hierarchie:

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
}

Inną wadą jest to, że nie ma sposobu na uzyskanie dostępu do nazwy zmiennej wyliczenia sealed obiektu ani na jej wyszukiwanie. Jeśli potrzebujesz nazwy powiązanej z każdą wartością, musisz ją zdefiniować ręcznie:

  sealed trait WeekDay { val name: String }

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

Lub tylko:

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

Używanie zapieczętowanych obiektów cechy i sprawy oraz allValues-macro

Jest to tylko rozszerzenie wariantu zapieczętowanej cechy, w którym makro generuje zestaw ze wszystkimi instancjami w czasie kompilacji. To ładnie pomija wadę, że programista może dodać wartość do wyliczenia, ale zapomina dodać ją do zestawu allElements.

Ten wariant szczególnie przydaje się w przypadku dużych enum.

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

Aby to zadziałało, potrzebujesz tego makra:

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow