【问题标题】:Iteration over a sealed trait in Scala?迭代Scala中的密封特征?
【发布时间】:2012-12-02 17:24:06
【问题描述】:

我只是想知道是否可以在 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 中是否有等价物?

【问题讨论】:

  • this不是你想要的吗?
  • 谢谢!试图理解为什么我不能使用案例对象;)

标签: scala enumeration scala-macros sealed


【解决方案1】:

在我看来,这实际上是 2.10 宏的合适用例:您希望访问您知道编译器具有但未公开的信息,而宏为您提供了一种(相当)简单的方法来窥视内部。请参阅我的回答 here 以获取相关(但现在稍微过时)的示例,或者仅使用以下内容:

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)

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

【讨论】:

  • 是的,Java 的 enum 可以说比 Scala 的 Enumeration 更不像火车残骸,在这方面比密封特征更方便,但我仍然会选择类似 Scala ADT在一周中的任何一天接近enum
  • 实际上这个宏中有一个错误,正如stackoverflow.com/questions/18732362/… 所强调的那样。它的最后几行应替换为gist.github.com/xeno-by/6573434
  • 已修复(最后——我很抱歉花了这么长时间才注意到评论)。谢谢,尤金!
  • 这个宏对我不起作用。我使用 scala 2.11.2 SealedExample.values[Rank] 我得到:类型不匹配;发现:scala.collection.immutable.Set[Product with Serializable with Rank] required: Set[Rank] Note: Product with Serializable with Rank <: rank but trait set a>_ <: Rank。 (SLS 3.2.10) sheet.sc /playground/src 第 8 行 Scala 问题
  • 经过@TravisBrown 的批准,我刚刚将此答案打包到一个小型库中并发布到 Bintray。来源:github.com/mrvisser/sealerateBintray:bintray.com/pellucid/maven/sealerate/view/general
【解决方案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】:

    看看@TravisBrown 的question 从 shapeless 2.1.0-SNAPSHOT 开始,他的问题中发布的代码可以工作并生成枚举的 ADT 元素的Set,然后可以遍历这些元素。为了便于参考,我将在这里回顾他的解决方案(fetchAllsort of mine :-))

    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
    

    【讨论】:

    • 我想在您的回答中补充一点,我们不需要为某些特征 T 隐式使用它来实现 EnumerableAdt[T]。一切都将仅由于宏生成而起作用。我认为这里并不明显。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      看到这个answer in another thread。 Lloydmetas Enumeratum libraryan easily available package 中提供类似 java Enum 的功能,但样板相对较少。

      【讨论】:

        【解决方案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)
        }
        

        【讨论】:

          猜你喜欢
          • 2015-05-26
          • 2017-09-08
          • 2015-09-17
          • 1970-01-01
          • 2018-12-18
          • 2016-07-18
          • 2019-04-04
          • 1970-01-01
          • 2016-03-29
          相关资源
          最近更新 更多