迭代 Scala 中的密封特征?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13671734/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 04:45:35  来源:igfitidea点击:

Iteration over a sealed trait in Scala?

scalaenumerationscala-macrossealed

提问by Sebastien Lorber

I just wanted to know if it is possible to iterate over a sealed trait in Scala? If not, why is it not possible? Since the trait is sealed it should be possible no?

我只是想知道是否可以在 Scala 中迭代一个密封的特征?如果不能,为什么不能?既然特性是密封的,那应该是可能的吧?

What I want to do is something like that:

我想做的是这样的:

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

What I want can be done in Java by giving an implementation to the enum values. Is there an equivalent in Scala?

我想要的可以通过为枚举值提供实现来在 Java 中完成。Scala 中是否有等价物?

回答by Travis Brown

This is actually in my opinion an appropriate use case for 2.10 macros: you want access to information that you know the compiler has, but isn't exposing, and macros give you a (reasonably) easy way to peek inside. See my answer herefor a related (but now slightly out-of-date) example, or just use something like this:

在我看来,这实际上是 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(_))
        )
      }
    }
  }
}

Now we can write the following:

现在我们可以写如下:

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

And this is all perfectly safe—you'll get a compile-time error if you ask for values of a type that isn't sealed, has non-object children, etc.

而且这一切都是完全安全的——如果您请求未密封、具有非对象子对象等类型的值,您将收到编译时错误。

回答by user673551

The above mentioned solution based on Scala Macros works great. However it does not cases like :

上面提到的基于 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]
}                                                 

To allow this, one can use this code:

为了允许这一点,可以使用以下代码:

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

回答by Yaneeve

Take a look at @TravisBrown's questionAs of shapeless 2.1.0-SNAPSHOT the code posted in his question works and produces a Setof the enumerated ADT elements which can then be traversed. I will recap his solution here for ease of reference (fetchAllis sort of mine:-))

看看@TravisBrown 的问题,从shapeless 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

回答by Dave Griffith

There's no capability for this natively. It wouldn't make sense in the more common case, where instead of case objects you had actual classes as subclass of your sealed trait. It looks like your case might be better handled by an enumeration

本机没有此功能。在更常见的情况下,这是没有意义的,在这种情况下,您将实际类作为密封特征的子类而不是 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

Alternatively, you could create an enumeration on your own, possibly placing it in the companion object for convenience

或者,您可以自己创建一个枚举,为了方便起见,可能会将其放置在伴随对象中

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

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

回答by Peter Lamberg

See this answer in another thread. The Lloydmetas Enumeratum libraryprovides java Enum like features in an easily available packagewith relatively little boilerplate.

在另一个线程中查看此答案。Lloydmetas Enumeratum 库一个易于获得的包中提供类似 java Enum 的功能,并且样板相对较少。

回答by Sebastien Lorber

Something that can also solve the problem is the possibility to add an implicit convertion to add methods to the enum, instead of iteraring over the sealed trait.

也可以解决这个问题的是,可以添加一个隐式转换来向枚举添加方法,而不是遍历密封的 trait。

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