2012-12-02 14 views
30

Volevo solo sapere se è possibile iterare su un tratto sigillato in Scala? In caso contrario, perché non è possibile? Dal momento che il tratto è sigillato, dovrebbe essere possibile no?Iterazione su un tratto sigillato in Scala?

Quello che voglio fare è qualcosa di simile:

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

Quello che voglio può essere fatto in Java, dando un'implementazione per i valori enum. Esiste un equivalente in Scala?

+1

Non è [questo] (https://gist.github.com/ea5e46a2f392204993fa) ciò che si vuole? –

+0

grazie! Stavo cercando di capire perché non potevo usare gli oggetti del caso;) –

risposta

50

Questo è in realtà in La mia opinione è un caso d'uso appropriato per i macro 2.10: si desidera accedere alle informazioni che si conoscono, ma che il compilatore non mostra, e le macro offrono un modo (ragionevolmente) facile di sbirciare all'interno. Vedere la mia risposta here per un relativo (ma ormai un po 'out-of-date) esempio, o semplicemente usare qualcosa di simile:

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

Ora possiamo scrivere la seguente:

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

E questo è tutto perfettamente sicuro - riceverai un errore in fase di compilazione se chiedi valori di un tipo che non è sigillato, ha figli non oggetto, ecc.

+0

Bello :) Non è così facile come facciamo in Java, ma avrà il compito finito (quando userò 2.10 ... –

+5

Sì, l'enum di Java è probabilmente meno un disastro di Scala di Enumerazione di Scala, ed è più conveniente dei tratti sigillati in questo particolare aspetto, ma sceglierei comunque l'approccio simile a Scala ADT su "enum" giorno della settimana. –

+5

In realtà c'è un bug in questa macro come evidenziato da http://stackoverflow.com/questions/18732362/issue-with-using-macros-in-sbt. Le sue ultime righe devono essere sostituite con https://gist.github.com/xeno-by/6573434. –

3

Non esiste alcuna possibilità per questo in modo nativo. Non avrebbe senso nel caso più comune, dove invece di oggetti del caso hai avuto classi reali come sottoclasse del tuo tratto sigillato. Sembra che il vostro caso potrebbe essere meglio gestita da un'enumerazione

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 

In alternativa, è possibile creare un'enumerazione da soli, possibilmente mettendolo in oggetto associato per comodità

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

println(ResizedImageKey.values.mkString(",") //prints Small,Medium,Large 
+0

grazie. Questo è quello che ho fatto: creare un elenco di valori –

0

Qualcosa che può anche risolvere il problema è la possibilità di aggiungi una conversione implicita per aggiungere metodi all'enum, anziché iteraring ov er il tratto sigillato.

8

La soluzione sopra menzionata basata su Scala Macro funziona benissimo. Tuttavia non è così casi come:

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

Per consentire a questo, si può usare questo codice:

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

Dare un'occhiata a @ TravisBrown's question A partire da 2.1.0-SNAPSHOT informe il codice pubblicato nella sua domanda funziona e produce un Set degli elementi ADT enumerati che possono quindi essere attraversati. Io ricapitolare la sua soluzione qui per comodità di riferimento (fetchAll è 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