如何在Scala中实现类型安全的可扩展类型系统?

时间:2012-12-30 05:51:30

标签: scala types extensibility

我在Scala写一个项目。该项目涉及一组功能和一组配置,两者都是可扩展的。通过“可扩展”,我的意思是我稍后将在层次结构中添加新功能,并且它们必须在不重新编译的情况下使用任何配置。以下是以下插图的功能层次结构:

trait Feature {
    def apply(board: Board)
}

class Foo extends Feature {
    def apply(board: Board) {
        println(board formatted "Foo: %s")
    }
}

class Bar extends Feature {
    def apply(board: Board) {
        println(board formatted "Bar: %s")
    }
}

配置基本上只为Board定义了很多参数,包括每个Feature的初始要素数。在运行时创建配置有几种可能的策略:预定义,随机,用户提供的值等。理论上,我希望能够编写类似这样的内容(不是有效的Scala代码!):

abstract class Config(val param: Int) {
    val ConfigParameter: Int
    def featureCount[T <: Feature]: Int
}

object Config {
    def makeBasic(param: Int) = new Config(param) {
        val ConfigParameter = param
        def featureCount[Foo] = 3
        def featureCount[Bar] = 7
    }
    def makeRandom(param: Int) = new Config(param) { ... }
    def makeWithUserValues(param: Int, ...) = new Config(param) { ... }
    def makeByStandardISO1234567(param: Int) = new Config(param) { ... }
}

class Board(val config: Config) { ... }

显然,它没有编译。我的问题是:在Scala中表示这个可扩展系统的最佳方法是什么?我总是可以在Map[Class, Int]中包含Config之类的内容,但它不是类型安全的:程序员可以在Map中插入不是Feature s的类。那么,Scala类型系统中是否有一种方法可以表示Map[Class[T <: Feature], Int]之类的内容,其中Map中的不同键可能具有不同的Feature个子类型?或者,也许有一些方法可以将所有这些行为移到Feature层次结构中?

谢谢。

3 个答案:

答案 0 :(得分:2)

您可以使用ClassManifest(Scala 2.10中的ClassTag)来改进地图解决方案:

package object features {
  type FeatureMap = Map[Class[_ <: Feature], Int]
}

abstract class Config(val param: Int) {
    def ConfigParameter: Int
    def featureMap: FeatureMap
    def featureCount[T<:Feature]( implicit man: ClassManifest[T] ): Int = 
      featureMap( man.erasure )
}

object Config {
    def makeBasic(param: Int) = new Config(param) {
        val ConfigParameter = param
        lazy val featureMap: FeatureMap = Map(
            classOf[Foo] -> 3,
            classOf[Bar] -> 7
        )
    }
}

每次调用featureCount时,编译器都会使用正确的classManifest作为括号之间传递的类型。 erasure方法返回相应的类。

备注:避免使用抽象的val,它会产生恼人的效果,并且会破坏二进制兼容性。

答案 1 :(得分:0)

我找到了基于Map的解决方案(虽然它可能不是最优雅的解决方案):

package object features {
    type FeatureMap = Map[Class[_ <: Feature], Int]
}

abstract class Config(val param: Int) {
    val ConfigParameter: Int
    val FeatureCount: FeatureMap
}

object Config {
    def makeBasic(param: Int) = new Config(param) {
        val ConfigParameter = param
        lazy val FeatureCount: FeatureMap = Map(
            classOf[Foo] -> 3,
            classOf[Bar] -> 7
        )
    }
}

答案 2 :(得分:0)

您确定需要地图了解每个密钥的特定子类型Feature吗?例如,这是我写这个的方式:

trait Feature { def apply(board: Board) }

case object Foo extends Feature {
  def apply(board: Board) { printf("Foo: %s", board) }
}

case object Bar extends Feature {
  def apply(board: Board) { printf("Bar: %s", board) }
}

// ...
val featureCount: Map[Feature, Int] = Map(Foo -> 3, Bar -> 7)

现在您可以只写featureCount(Foo)featureCount.get(Bar)等。如果您想确保没有非对象值可以潜入,您甚至可以将地图键入Map[Feature with Singleton, Int]

或者,如果你真的想要一个键入类型的地图,你可以用Shapeless做这样的事情:

trait Feature

case class Foo(count: Int) extends Feature
case class Bar(count: Int) extends Feature

import shapeless._

object featureWithCount extends Poly0 {
  implicit def foo = at(Foo(3))
  implicit def bar = at(Bar(7))
}

然后:

scala> featureWithCount[Foo]
res0: Foo = Foo(3)

scala> featureWithCount[Bar]
res1: Bar = Bar(7)

我不确定在这里买多少。