Итерация по запечатанному признаку в Scala?
Я просто хотел узнать, можно ли перебирать запечатанную черту в Scala?
Если нет, то почему это невозможно? Поскольку черта запечатана, это должно быть возможно?
Я хочу сделать что-то вроде этого:
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)
}
То, что я хочу, можно сделать на Java, предоставив реализацию значениям перечисления. Существует ли эквивалент в Scala?
Ответы
Ответ 1
Это, на мой взгляд, подходящий вариант использования для макросов 2.10: вам нужен доступ к информации, которую вы знаете, компилятор имеет, но не подвергается экспонату, а макросы дают вам (разумно) простой способ заглянуть внутрь. См. Мой ответ здесь для соответствующего (но теперь слегка устаревшего) примера или просто используйте что-то вроде этого:
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(_))
)
}
}
}
}
Теперь мы можем написать следующее:
scala> val keys: Set[ResizedImageKey] = SealedExample.values[ResizedImageKey]
keys: Set[ResizedImageKey] = Set(Large, Medium, Small)
И все это совершенно безопасно - вы получите ошибку времени компиляции, если вы запрашиваете значения типа, который не запечатан, имеет неъектных детей и т.д.
Ответ 2
Вышеупомянутое решение на основе макросов Scala отлично работает. Однако это не такие случаи, как:
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]
}
Чтобы это разрешить, можно использовать этот код:
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(_))
)
}
}
}
}
Ответ 3
Там нет возможности для этого. Это не имело бы смысла в более распространенном случае, где вместо объектов корпуса у вас были фактические классы в качестве подкласса вашей запечатанной черты. Похоже, ваш случай может быть лучше обработан перечислением
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
В качестве альтернативы вы можете создать перечисление самостоятельно, возможно, поместив его в компаньон для удобства
object ResizedImageKey{
val values = Vector(Small, Medium, Large)
}
println(ResizedImageKey.values.mkString(",") //prints Small,Medium,Large
Ответ 4
См. этот ответ в другом потоке. Lloydmetas Библиотека Enumeratum предоставляет java Enum как функции в легко доступный пакет с относительно небольшим количеством шаблонов.
Ответ 5
Взгляните на @TravisBrown question. Из бесформенного 2.1.0-SNAPSHOT код, отправленный в его вопросе, работает и производит Set
перечисляемого ADT, которые затем могут быть пройдены. Я приведу его решение здесь для удобства ссылок (fetchAll
is вид моего: -))
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
Ответ 6
Что-то, что может также решить проблему, - это возможность добавить неявное преобразование для добавления методов в перечисление, вместо того, чтобы повторять запечатанную черту.
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)
}