I just wanted to know if it is possible to iterate over a sealed trait in Scala? If not, why is it not possible? Since the trait is sealed it should be possible no?
What I want to do is something like that:
sealed trait ResizedImageKey {
/**
* Get the dimensions to use on the resized image associated with this key
*/
def getDimension(originalDimension: Dimension): Dimension
}
case class Dimension(width: Int, height: Int)
case object Large extends ResizedImageKey {
def getDimension(originalDimension: Dimension) = Dimension(1000,1000)
}
case object Medium extends ResizedImageKey{
def getDimension(originalDimension: Dimension) = Dimension(500,500)
}
case object Small extends ResizedImageKey{
def getDimension(originalDimension: Dimension) = Dimension(100,100)
}
What I want can be done in Java by giving an implementation to the enum values. Is there an equivalent in Scala?
This is actually in my opinion an appropriate use case for 2.10 macros: you want access to information that you know the compiler has, but isn't exposing, and macros give you a (reasonably) easy way to peek inside. See my answer here for a related (but now slightly out-of-date) example, or just use something like this:
import language.experimental.macros
import scala.reflect.macros.Context
object SealedExample {
def values[A]: Set[A] = macro values_impl[A]
def values_impl[A: c.WeakTypeTag](c: Context) = {
import c.universe._
val symbol = weakTypeOf[A].typeSymbol
if (!symbol.isClass) c.abort(
c.enclosingPosition,
"Can only enumerate values of a sealed trait or class."
) else if (!symbol.asClass.isSealed) c.abort(
c.enclosingPosition,
"Can only enumerate values of a sealed trait or class."
) else {
val children = symbol.asClass.knownDirectSubclasses.toList
if (!children.forall(_.isModuleClass)) c.abort(
c.enclosingPosition,
"All children must be objects."
) else c.Expr[Set[A]] {
def sourceModuleRef(sym: Symbol) = Ident(
sym.asInstanceOf[
scala.reflect.internal.Symbols#Symbol
].sourceModule.asInstanceOf[Symbol]
)
Apply(
Select(
reify(Set).tree,
newTermName("apply")
),
children.map(sourceModuleRef(_))
)
}
}
}
}
Now we can write the following:
scala> val keys: Set[ResizedImageKey] = SealedExample.values[ResizedImageKey]
keys: Set[ResizedImageKey] = Set(Large, Medium, Small)
And this is all perfectly safe—you'll get a compile-time error if you ask for values of a type that isn't sealed, has non-object children, etc.
The above mentioned solution based on Scala Macros works great. However it does not cases like :
sealed trait ImageSize
object ImageSize {
case object Small extends ImageSize
case object Medium extends ImageSize
case object Large extends ImageSize
val values = SealedTraitValues.values[ImageSize]
}
To allow this, one can use this code:
import language.experimental.macros
import scala.reflect.macros.Context
object SealedExample {
def values[A]: Set[A] = macro values_impl[A]
def values_impl[A: c.WeakTypeTag](c: Context) = {
import c.universe._
val symbol = weakTypeOf[A].typeSymbol
if (!symbol.isClass) c.abort(
c.enclosingPosition,
"Can only enumerate values of a sealed trait or class."
) else if (!symbol.asClass.isSealed) c.abort(
c.enclosingPosition,
"Can only enumerate values of a sealed trait or class."
) else {
val siblingSubclasses: List[Symbol] = scala.util.Try {
val enclosingModule = c.enclosingClass.asInstanceOf[ModuleDef]
enclosingModule.impl.body.filter { x =>
scala.util.Try(x.symbol.asModule.moduleClass.asClass.baseClasses.contains(symbol))
.getOrElse(false)
}.map(_.symbol)
} getOrElse {
Nil
}
val children = symbol.asClass.knownDirectSubclasses.toList ::: siblingSubclasses
if (!children.forall(x => x.isModuleClass || x.isModule)) c.abort(
c.enclosingPosition,
"All children must be objects."
) else c.Expr[Set[A]] {
def sourceModuleRef(sym: Symbol) = Ident(
if (sym.isModule) sym else
sym.asInstanceOf[
scala.reflect.internal.Symbols#Symbol
].sourceModule.asInstanceOf[Symbol]
)
Apply(
Select(
reify(Set).tree,
newTermName("apply")
),
children.map(sourceModuleRef(_))
)
}
}
}
}
Take a look at @TravisBrown's question As of shapeless 2.1.0-SNAPSHOT the code posted in his question works and produces a Set
of the enumerated ADT elements which can then be traversed. I will recap his solution here for ease of reference (fetchAll
is sort of mine :-))
import shapeless._
trait AllSingletons[A, C <: Coproduct] {
def values: List[A]
}
object AllSingletons {
implicit def cnilSingletons[A]: AllSingletons[A, CNil] =
new AllSingletons[A, CNil] {
def values = Nil
}
implicit def coproductSingletons[A, H <: A, T <: Coproduct](implicit
tsc: AllSingletons[A, T],
witness: Witness.Aux[H]
): AllSingletons[A, H :+: T] =
new AllSingletons[A, H :+: T] {
def values: List[A] = witness.value :: tsc.values
}
}
trait EnumerableAdt[A] {
def values: Set[A]
}
object EnumerableAdt {
implicit def fromAllSingletons[A, C <: Coproduct](implicit
gen: Generic.Aux[A, C],
singletons: AllSingletons[A, C]
): EnumerableAdt[A] =
new EnumerableAdt[A] {
def values: Set[A] = singletons.values.toSet
}
}
def fetchAll[T](implicit ev: EnumerableAdt[T]):Set[T] = ev.values
There's no capability for this natively. It wouldn't make sense in the more common case, where instead of case objects you had actual classes as subclass of your sealed trait. It looks like your case might be better handled by an enumeration
object ResizedImageKey extends Enumeration {
type ResizedImageKey = Value
val Small, Medium, Large = Value
def getDimension(value:ResizedImageKey):Dimension =
value match{
case Small => Dimension(100, 100)
case Medium => Dimension(500, 500)
case Large => Dimension(1000, 1000)
}
println(ResizedImageKey.values.mkString(",") //prints Small,Medium,Large
Alternatively, you could create an enumeration on your own, possibly placing it in the companion object for convenience
object ResizedImageKey{
val values = Vector(Small, Medium, Large)
}
println(ResizedImageKey.values.mkString(",") //prints Small,Medium,Large
See this answer in another thread. The Lloydmetas Enumeratum library provides java Enum like features in an easily available package with relatively little boilerplate.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With