我在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
层次结构中?
谢谢。
答案 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)
我不确定在这里买多少。