3

我正在用 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")
    }
}

配置基本上只是为 a 定义了很多参数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]的东西,但它不是类型安全的:程序员可以在非sConfig中插入类。那么,在 Scala 类型系统中是否有一种方法可以表示类似其中的不同键可能是不同子类型的东西?或者,也许有一些方法可以将所有这些行为转移到层次结构中?MapFeatureMap[Class[T <: Feature], Int]MapFeatureFeature

谢谢你。

4

3 回答 3

2

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

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方法返回对应的类。

备注:避免抽象 vals,它有烦人的效果,并且会破坏二进制兼容性。

于 2012-12-30T09:23:57.967 回答
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
        )
    }
}
于 2012-12-30T07:44:00.987 回答
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)

不过,我不确定这对你有什么好处。

于 2012-12-30T14:45:08.180 回答