在Scala中对密封特性进行迭代?

Seb*_*ber 38 enumeration scala sealed scala-macros

我只是想知道是否有可能在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)
}
Run Code Online (Sandbox Code Playgroud)

通过给枚举值赋予实现,我可以用Java完成.Scala中有同等的东西吗?

Tra*_*own 60

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

现在我们可以写下面的内容:

scala> val keys: Set[ResizedImageKey] = SealedExample.values[ResizedImageKey]
keys: Set[ResizedImageKey] = Set(Large, Medium, Small)
Run Code Online (Sandbox Code Playgroud)

这一切都非常安全 - 如果您要求未密封的类型的值,具有非对象子项等,您将获得编译时错误.

  • 是的,Java的"enum"可以说比Scala的"Enumeration"少了一个火车残骸,并且比这个特殊方面的密封特性更方便,但是我仍然会在任何一天选择Scala ADT类似于enum的方法.星期. (6认同)
  • 经过@TravisBrown的批准,我把这个答案打包成一个小型图书馆并发布给Bintray.资料来源:https://github.com/mrvisser/sealerate Bintray:https://bintray.com/pellucid/maven/sealerate/view/general (6认同)
  • 实际上这个宏中存在一个错误,正如http://stackoverflow.com/questions/18732362/issue-with-using-macros-in-sbt所强调的那样.其最后一行应替换为https://gist.github.com/xeno-by/6573434. (5认同)
  • 固定(最后 - 我很抱歉花了这么长时间才注意到评论).谢谢,尤金! (3认同)
  • 这个宏对我不起作用.我使用scala 2.11.2 SealedExample.values [Rank]我得到:类型不匹配; 发现:scala.collection.immutable.Set [产品与序列化与等级]要求:设置[等级]注:产品与序列化以秩<:等级,但特质集是A型不变的你不妨调查通配符类型例如`_ <:Rank`.(SLS 3.2.10)sheet.sc/playground/src第8行Scala问题 (2认同)

小智 8

基于Scala Macros的上述解决方案效果很好.然而,它不像以下情况:

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]
}                                                 
Run Code Online (Sandbox Code Playgroud)

为此,可以使用以下代码:

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(_))
                )
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Yan*_*eve 5

看一下 @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
Run Code Online (Sandbox Code Playgroud)