Вопрос по scala-macros, enumeration, scala, sealed – Итерация по запечатанному признаку в Scala?

33

Я просто хотел узнать, возможно ли перебрать запечатанную черту в 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, предоставив реализацию значений enum. Есть ли эквивалент в Scala?

Спасибо! Пытался понять, почему я не мог использовать объекты дела;) Sebastien Lorber
неэто то, что ты хочешь? Tomasz Nurkiewicz

Ваш Ответ

6   ответов
8

основанное на макросах 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(_))
                )
            }
        }
    }
}
1

ответ в другой теме, ЛлойдметасПеречень библиотек обеспечивает java Enum как функции влегко доступный пакет с относительно небольшим шаблоном.

1

Взгляните на @ TravisBrown'sвопрос Начиная с бесформенного 2.1.0-SNAPSHOT, код, размещенный в его вопросе, работает и производитSet перечисленных элементов ADT, которые затем могут быть пройдены. Я напишу его решение здесь для простоты ссылки (fetchAll являетсясвоего рода мой :-))

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
0

что также может решить проблему, - это возможность добавить неявное преобразование для добавления методов в перечисление, вместо того, чтобы выполнять итерацию по запечатанному признаку.

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

ее распространенном случае, когда вместо объектов case у вас были реальные классы в качестве подкласса вашей запечатанной черты. Похоже, что ваш случай может быть лучше обработан перечислением

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
Благодарю. Это то, что я сделал: создание списка значений Sebastien Lorber
54

на мой взгляд, это подходящий вариант использования макросов 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)

И все это совершенно безопасно - вы получите ошибку во время компиляции, если вы запросите значения типа, который не запечатан, имеет необъектные дочерние элементы и т. Д.

Этот макрос не работает для меня. Я использую scala 2.11.2 SealedExample.values [Ранг] Я получаю: несоответствие типов; Найдено: scala.collection.immutable.Set Требуется [Продукт с сериализуемым с рангом]: Установить [Ранг] Примечание: Продукт с сериализуемым с рангом <: Ранг, но набор признаков является инвариантным для типа А. Возможно, вы захотите исследовать подстановочный тип такие как_ <: Rank, (SLS 3.2.10) sheet.sc / plays / src line 8 Проблема Scala Anton Kuzmin
С одобрения @TravisBrown я только что упаковал этот ответ в крошечную библиотеку и опубликовал в Bintray. Источник:github.com/mrvisser/sealerate Bintray:bintray.com/pellucid/maven/sealerate/view/general Charlie Brown
Исправлено (наконец-то - мои извинения за то, что я так долго заметил комментарий). Спасибо, Евгений! Travis Brown
Да, яваenum возможно меньше крушения поезда, чем СкалаEnumeration, и это более удобно, чем закрытые черты в этом конкретном отношении, но я бы все-таки выбрал подход, подобный ADT в Scala.enum любой день недели. Travis Brown
На самом деле есть ошибка в этом макросе, как выделеноstackoverflow.com/questions/18732362/..., Его последние строки должны быть заменены наgist.github.com/xeno-by/6573434. Eugene Burmako

Похожие вопросы