Iteracja nad zapieczętowaną cechą w Scali?
Chciałem tylko wiedzieć, czy możliwe jest iterację nad zapieczętowaną cechą w Scali? Jeśli nie, to dlaczego nie jest to możliwe? Skoro cecha jest zapieczętowana, powinno być możliwe, nie?
To co chcę zrobić to coś takiego:
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)
}
To, co chcę, można zrobić w Javie, dając implementację wartości enum. Czy jest jakiś odpowiednik w Scali?
6 answers
Jest to właściwie moim zdaniem odpowiedni przypadek użycia dla makr 2.10: chcesz uzyskać dostęp do informacji, które wiesz, że kompilator ma, ale nie ujawnia, a makra dają (w miarę) łatwy sposób zajrzeć do środka. Zobacz moją odpowiedź tutaj dla pokrewnego (ale teraz nieco nieaktualnego) przykładu, lub po prostu użyj czegoś takiego:
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(_))
)
}
}
}
}
Teraz możemy napisać:
scala> val keys: Set[ResizedImageKey] = SealedExample.values[ResizedImageKey]
keys: Set[ResizedImageKey] = Set(Large, Medium, Small)
I to wszystko jest całkowicie bezpieczne-dostaniesz błąd w czasie kompilacji, jeśli poprosisz o wartości typ, który nie jest zapieczętowany, nie ma obiektów potomnych itp.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:36
Powyższe rozwiązanie oparte na makrach Scala działa świetnie. Nie ma jednak takich przypadków jak:
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]
}
Aby to umożliwić, można użyć tego kodu:
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(_))
)
}
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-11 19:27:39
Nie ma możliwości tego natywnie. Nie miałoby to sensu w bardziej powszechnym przypadku, gdzie zamiast obiektów case masz rzeczywiste klasy jako podklasę swojej zapieczętowanej cechy. Wygląda na to, że Twoja sprawa może być lepiej załatwiona przez wyliczenie
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
Alternatywnie, możesz utworzyć wyliczenie na własną rękę, ewentualnie umieszczając je w obiekcie towarzyszącym dla wygody
object ResizedImageKey{
val values = Vector(Small, Medium, Large)
}
println(ResizedImageKey.values.mkString(",") //prints Small,Medium,Large
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-02 17:51:56
Zobacz tą odpowiedź w innym wątku . Biblioteka Lloydmetas Enumeratum udostępnia funkcje java Enum like w łatwo dostępnym pakiecie ze stosunkowo małą ilością kotłów.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:33:13
Spójrz na pytanie @TravisBrown od shapeless 2.1.0-migawka kodu zamieszczonego w jego pytaniu działa i tworzy Set
z wymienionych elementów ADT, które można następnie przejść. Podsumuję jego rozwiązanie tutaj dla ułatwienia odniesienia (fetchAll
jest coś w rodzaju mojego :-))
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:59
Czymś, co może również rozwiązać problem, jest możliwość dodania implicit convertion, aby dodać metody do enum, zamiast iteracji nad zapieczętowaną cechą.
object SharingPermission extends Enumeration {
val READ = Value("READ")
val WRITE = Value("WRITE")
val MANAGE = Value("MANAGE")
}
/**
* Permits to extend the enum definition and provide a mapping betweet SharingPermission and ActionType
* @param permission
*/
class SharingPermissionExtended(permission: SharingPermission.Value) {
val allowRead: Boolean = permission match {
case SharingPermission.READ => true
case SharingPermission.WRITE => true
case SharingPermission.MANAGE => true
}
val allowWrite: Boolean = permission match {
case SharingPermission.READ => false
case SharingPermission.WRITE => true
case SharingPermission.MANAGE => true
}
val allowManage: Boolean = permission match {
case SharingPermission.READ => false
case SharingPermission.WRITE => false
case SharingPermission.MANAGE => true
}
def allowAction(actionType: ActionType.Value): Boolean = actionType match {
case ActionType.READ => allowRead
case ActionType.WRITE => allowWrite
case ActionType.MANAGE => allowManage
}
}
object SharingPermissionExtended {
implicit def conversion(perm: SharingPermission.Value): SharingPermissionExtended = new SharingPermissionExtended(perm)
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-22 08:21:38