¿Iteración sobre un rasgo sellado en Scala?

Resuelto Sebastien Lorber asked hace 11 años • 6 respuestas

Solo quería saber si es posible iterar sobre un rasgo sellado en Scala. Si no, ¿por qué no es posible? Dado que el rasgo está sellado, debería ser posible, ¿no?

Lo que quiero hacer es algo como esto:

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

Lo que quiero se puede hacer en Java dando una implementación a los valores de enumeración. ¿Existe un equivalente en Scala?

Sebastien Lorber avatar Dec 03 '12 00:12 Sebastien Lorber
Aceptado

En mi opinión, este es en realidad un caso de uso apropiado para las macros 2.10: desea acceder a información que sabe que tiene el compilador, pero que no expone, y las macros le brindan una manera (razonablemente) fácil de echar un vistazo al interior. Vea mi respuesta aquí para ver un ejemplo relacionado (pero ahora un poco desactualizado), o simplemente use algo como esto:

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

Ahora podemos escribir lo siguiente:

scala> val keys: Set[ResizedImageKey] = SealedExample.values[ResizedImageKey]
keys: Set[ResizedImageKey] = Set(Large, Medium, Small)

Y todo esto es perfectamente seguro: obtendrá un error en tiempo de compilación si solicita valores de un tipo que no está sellado, que tiene hijos que no son objetos, etc.

Travis Brown avatar Dec 02 '2012 18:12 Travis Brown

La solución mencionada anteriormente basada en Scala Macros funciona muy bien. Sin embargo, no ocurre en casos como:

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

Para permitir esto, se puede usar este código:

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(_))
                )
            }
        }
    }
}
user673551 avatar Jul 11 '2014 19:07 user673551

Eche un vistazo a la pregunta de @TravisBrown. A partir de Shapeless 2.1.0-SNAPSHOT, el código publicado en su pregunta funciona y produce uno Setde los elementos ADT enumerados que luego se pueden atravesar. Recapitularé su solución aquí para facilitar la referencia ( fetchAlles más o menos mía :-))

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
Yaneeve avatar Apr 27 '2017 13:04 Yaneeve

No hay capacidad para esto de forma nativa. No tendría sentido en el caso más común, donde en lugar de objetos de caso tenías clases reales como subclase de tu rasgo sellado. Parece que su caso podría manejarse mejor mediante una enumeración.

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

Alternativamente, puede crear una enumeración por su cuenta, posiblemente colocándola en el objeto complementario para mayor comodidad.

object ResizedImageKey{
  val values = Vector(Small, Medium, Large)
}

println(ResizedImageKey.values.mkString(",") //prints Small,Medium,Large
Dave Griffith avatar Dec 02 '2012 17:12 Dave Griffith