2012-12-02 17 views
30

Ich wollte nur wissen, ob es möglich ist, über eine versiegelte Eigenschaft in Scala zu iterieren? Wenn nicht, warum ist das nicht möglich? Da das Merkmal versiegelt ist, sollte es möglich sein, nein?Iteration über eine versiegelte Eigenschaft in Scala?

Was ich tun möchte, ist so etwas wie das:

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

Was ich will, kann, indem eine Implementierung auf die Enum-Werte in Java erfolgen. Gibt es ein Äquivalent in Scala?

+1

Ist das nicht [diese] (https://gist.github.com/ea5e46a2f392204993fa), was Sie wollen? –

+0

danke! Versuchte zu verstehen, warum ich die case-Objekte nicht benutzen konnte;) –

Antwort

50

Dies ist eigentlich in meiner Meinung nach ein geeigneter Anwendungsfall für 2.10-Makros: Sie wollen Zugriff auf Informationen, von denen Sie wissen, dass sie der Compiler hat, aber nicht aufdecken, und Makros geben Ihnen eine (vernünftige) einfache Möglichkeit, hineinzuschauen. Siehe meine Antwort here für ein verwandtes (aber jetzt leicht sind out-of-date) Beispiel, oder einfach nur so etwas wie folgt verwenden:

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

Jetzt können wir schreiben folgendes:

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

und das ist Alles vollkommen sicher - Sie erhalten einen Fehler bei der Kompilierung, wenn Sie nach Werten eines Typs fragen, der nicht versiegelt ist, nicht untergeordnete Objekte usw.

+0

Nice :) Es ist nicht so einfach wie in Java, aber es wird die Arbeit erledigt (wenn ich 2.10 ...) –

+5

Ja, Javas "enum" ist wohl weniger ein Zugwrack als Scalas "Enumeration" und ist in dieser Hinsicht angenehmer als versiegelte Züge, aber ich würde immer noch den Scala ADT-ähnlichen Ansatz über 'enum' any wählen Wochentag. –

+5

Tatsächlich gibt es einen Fehler in diesem Makro, wie von http://stackoverflow.com/questions/18732362/issue-with-using-macros-in-sbt hervorgehoben. Die letzten Zeilen sollten durch https://gist.github.com/xeno-by/6573434 ersetzt werden. –

3

Dies ist nativ nicht möglich. Es würde im allgemeineren Fall keinen Sinn ergeben, wenn anstelle von Fallobjekten tatsächliche Klassen als Unterklasse Ihres versiegelten Merkmals vorhanden wären. Es sieht aus wie der Fall sein könnte besser durch eine Aufzählung

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 

Alternativ behandelt werden, können Sie eine Aufzählung auf eigene Faust schaffen könnte, möglicherweise in dem Begleitobjekt für Bequemlichkeit Platzierung

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

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

danke. Das ist was ich getan habe: Erstellen einer Liste von Werten –

0

Etwas, das das Problem auch lösen kann, ist die Möglichkeit Fügen Sie eine implizite Konvertierung hinzu, um Methoden zur Enumeration hinzuzufügen, anstatt iterieren zu müssen Das versiegelte Merkmal.

8

Die oben genannte Lösung basierend auf Scala Macros funktioniert gut. Allerdings spielt es keine Fälle wie:

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

Um dies zu ermöglichen, kann man diesen Code verwenden:

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

Werfen Sie einen Blick auf @ TravisBrowns question Ab formlosen 2.1.0-SNAPSHOT funktioniert der Code in seiner Frage geschrieben und produziert eine Set der aufgelisteten ADT-Elemente, die dann durchlaufen werden können. Ich werde für eine einfache Referenz seine Lösung hier rekapitulieren (fetchAll ist 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