Scala Language
列挙型
サーチ…
備考
Scalaの列挙にはいくつかの問題があるため、 sealed trait
case objects
とcase objects
を使用するアプローチが優先されます。
- 列挙型は消去後も同じ型です。
- コンパイラは「マッチは網羅的ではありません」と不平を言っていません。大文字と
scala.MatchError
ないと実行時に失敗します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)
と比べて:
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 {
^
Scala Enumerationの詳細については、この記事で詳しく説明しています 。
Scala Enumerationを使用した曜日
Enumerationを拡張することによって、Javaのような列挙型を作成できます 。
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
列挙の値に人間が判読可能な名前を追加することもできます。
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
異なる列挙型が同じインスタンス型として評価されるような、型どおりではない動作に注意してください。
object Parity extends Enumeration {
val Even, Odd = Value
}
WeekDays.Mon.isInstanceOf[Parity.Value]
>> res1: Boolean = true
密封された特性オブジェクトとケースオブジェクトの使用
Enumeration
を拡張する代わりに、 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
}
sealed
キーワードは、特性WeekDay
が別のファイルで拡張できないことを保証します。これにより、 WeekDay
すべての可能な値が既に列挙されていることを含めて、コンパイラは特定の前提を設定できます。
1つの欠点は、このメソッドではすべての可能な値のリストを取得できないということです。このようなリストを取得するには、明示的に指定する必要があります。
val allWeekDays = Seq(Mon, Tue, Wed, Thu, Fri, Sun, Sat)
ケースクラスはsealed
特性を拡張することもできます。したがって、オブジェクトとケースクラスを混合して複雑な階層を作成することができます。
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
}
もう一つの欠点は、 sealed
オブジェクトの列挙体の変数名にアクセスする方法、またはそれによって検索する方法がないことです。各値に関連付けられた名前が必要な場合は、手動で定義する必要があります。
sealed trait WeekDay { val name: String }
object WeekDay {
case object Mon extends WeekDay { val name = "Monday" }
case object Tue extends WeekDay { val name = "Tuesday" }
(...)
}
あるいは単に:
sealed case class WeekDay(name: String)
object WeekDay {
object Mon extends WeekDay("Monday")
object Tue extends WeekDay("Tuesday")
(...)
}
密封された特性とケースオブジェクトとallValues-macroの使用
これは、マクロがコンパイル時にすべてのインスタンスを含むセットを生成する密封された特性変形の単なる拡張です。これは、開発者が列挙に値を追加できますが、それをallElementsセットに追加することを忘れてしまうという欠点を省いています。
この変形は、特に大きな列挙型の場合に便利です。
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]
}
これを行うには、次のマクロが必要です。
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(_))
)
}
}
}
}