Search code examples
scalaenumerationscala-macrossealed

Iteration over a sealed trait in Scala?


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?


Solution

  • 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.