我最近在处理 Scala 时发布了几个关于 SO 的问题traits https://stackoverflow.com/questions/11618915, 表示类型 https://stackoverflow.com/questions/11277656, 会员类型 https://stackoverflow.com/questions/11236122, 体现 https://stackoverflow.com/questions/11284538, and 隐性证据 https://stackoverflow.com/questions/11588014。这些问题背后是我为生物蛋白质网络构建建模软件的项目。尽管这些答案非常有帮助,让我比我自己能得到的答案更加接近,但我仍然没有为我的项目找到解决方案。几个答案表明我的设计有缺陷,这就是为什么解决方案Foo
- 框架问题在实践中不起作用。在这里,我发布了我的问题的更复杂(但仍然大大简化)的版本。我希望这个问题和解决方案对于那些试图在 Scala 中构建复杂的特征和类层次结构的人来说会广泛有用。
我的项目中最高级别的课程是生物反应规则。规则描述了一种或两种反应物如何通过反应而转化。每个反应物都是一个图,其中具有称为单体的节点和连接单体上命名位点的边。每个站点也有一个它可以处于的状态。编辑:边的概念已从示例代码中删除,因为它们使示例复杂化,但对问题没有太大贡献。一条规则可能是这样的:存在一种由单体 A 制成的反应物,分别通过位点 a1 和 b1 与单体 B 结合;规则破坏了键合,使站点 a1 和 b1 不受约束;同时在单体 A 上,位点 a1 的状态从 U 变为 P。我将其写为:
A(a1~U-1).B(b1-1) -> A(a1~P) + B(b1)
(在 Scala 中解析这样的字符串是如此简单,这让我头晕目眩。)-1
表示键 #1 位于这些位点之间——该数字只是一个任意标签。
以下是我到目前为止所掌握的内容以及添加每个组件的原因。它可以编译,但只能无偿使用asInstanceOf
。我该如何摆脱asInstanceOf
以便类型匹配?
我用一个基本类来表示规则:
case class Rule(
reactants: Seq[ReactantGraph], // The starting monomers and edges
producedMonomers: Seq[ProducedMonomer] // Only new monomers go here
) {
// Example method that shows different monomers being combined and down-cast
def combineIntoOneGraph: Graph = {
val all_monomers = reactants.flatMap(_.monomers) ++ producedMonomers
GraphClass(all_monomers)
}
}
图表类GraphClass
具有类型参数,因为这样我就可以对特定图中允许的单体和边类型进行限制;例如,不能有任何ProducedMonomer
是在Reactant
of a Rule
。我也希望能够collect
一切Monomer
特定类型的 s,例如ReactantMonomer
s。我使用类型别名来管理约束。
case class GraphClass[
+MonomerType <: Monomer
](
monomers: Seq[MonomerType]
) {
// Methods that demonstrate the need for a manifest on MonomerClass
def justTheProductMonomers: Seq[ProductMonomer] = {
monomers.collect{
case x if isProductMonomer(x) => x.asInstanceOf[ProductMonomer]
}
}
def isProductMonomer(monomer: Monomer): Boolean = (
monomer.manifest <:< manifest[ProductStateSite]
)
}
// The most generic Graph
type Graph = GraphClass[Monomer]
// Anything allowed in a reactant
type ReactantGraph = GraphClass[ReactantMonomer]
// Anything allowed in a product, which I sometimes extract from a Rule
type ProductGraph = GraphClass[ProductMonomer]
单体类MonomerClass
还有类型参数,以便我可以对站点施加约束;例如,一个ConsumedMonomer
不能有一个StaticStateSite
。此外,我需要collect
特定类型的所有单体,例如收集产品中规则中的所有单体,因此我添加Manifest
到每个类型参数。
case class MonomerClass[
+StateSiteType <: StateSite : Manifest
](
stateSites: Seq[StateSiteType]
) {
type MyType = MonomerClass[StateSiteType]
def manifest = implicitly[Manifest[_ <: StateSiteType]]
// Method that demonstrates the need for implicit evidence
// This is where it gets bad
def replaceSiteWithIntersection[A >: StateSiteType <: ReactantStateSite](
thisSite: A, // This is a member of this.stateSites
monomer: ReactantMonomer
)(
// Only the sites on ReactantMonomers have the Observed property
implicit evidence: MyType <:< ReactantMonomer
): MyType = {
val new_this = evidence(this) // implicit evidence usually needs some help
monomer.stateSites.find(_.name == thisSite.name) match {
case Some(otherSite) =>
val newSites = stateSites map {
case `thisSite` => (
thisSite.asInstanceOf[StateSiteType with ReactantStateSite]
.createIntersection(otherSite).asInstanceOf[StateSiteType]
)
case other => other
}
copy(stateSites = newSites)
case None => this
}
}
}
type Monomer = MonomerClass[StateSite]
type ReactantMonomer = MonomerClass[ReactantStateSite]
type ProductMonomer = MonomerClass[ProductStateSite]
type ConsumedMonomer = MonomerClass[ConsumedStateSite]
type ProducedMonomer = MonomerClass[ProducedStateSite]
type StaticMonomer = MonomerClass[StaticStateSite]
我目前的实施StateSite
没有类型参数;它是一个标准的特征层次结构,终止于具有名称和一些特征的类String
代表适当的状态。 (善意地使用字符串来保存对象状态;它们实际上是我真实代码中的名称类。)这些特征的一个重要目的是提供所有子类所需的功能。嗯,这不就是所有特质的目的吗?我的特征很特别,因为许多方法都会对特征的所有子类共有的对象属性进行一些小的更改,然后返回一个副本。如果返回类型与对象的基础类型匹配,那就更好了。执行此操作的蹩脚方法是将所有特征方法抽象化,并将所需的方法复制到所有子类中。我不确定执行此操作的正确 Scala 方法。一些来源建议使用会员类型MyType
存储基础类型(此处所示)。其他来源建议使用表示类型参数。
trait StateSite {
type MyType <: StateSite
def name: String
}
trait ReactantStateSite extends StateSite {
type MyType <: ReactantStateSite
def observed: Seq[String]
def stateCopy(observed: Seq[String]): MyType
def createIntersection(otherSite: ReactantStateSite): MyType = {
val newStates = observed.intersect(otherSite.observed)
stateCopy(newStates)
}
}
trait ProductStateSite extends StateSite
trait ConservedStateSite extends ReactantStateSite with ProductStateSite
case class ConsumedStateSite(name: String, consumed: Seq[String])
extends ReactantStateSite {
type MyType = ConsumedStateSite
def observed = consumed
def stateCopy(observed: Seq[String]) = copy(consumed = observed)
}
case class ProducedStateSite(name: String, Produced: String)
extends ProductStateSite
case class ChangedStateSite(
name: String,
consumed: Seq[String],
Produced: String
)
extends ConservedStateSite {
type MyType = ChangedStateSite
def observed = consumed
def stateCopy(observed: Seq[String]) = copy(consumed = observed)
}
case class StaticStateSite(name: String, static: Seq[String])
extends ConservedStateSite {
type MyType = StaticStateSite
def observed = static
def stateCopy(observed: Seq[String]) = copy(static = observed)
}
我最大的问题是像这样的方法MonomerClass.replaceSiteWithIntersection
。许多方法对类的特定成员进行一些复杂的搜索,然后将这些成员传递给其他函数,在这些函数中对它们进行复杂的更改并返回一个副本,然后该副本在更高级别对象的副本中替换原始成员。我应该如何参数化方法(或类)以便调用是类型安全的?现在我只能用很多东西来编译代码asInstanceOf
到处都是。 Scala 对传递类型或成员参数的实例特别不满意,因为我可以看到两个主要原因:(1) 协变类型参数最终作为任何将它们作为输入的方法的输入,(2) 它是很难让 Scala 相信返回副本的方法确实返回与放入的类型完全相同的对象。
毫无疑问,我留下了一些大家都不清楚的事情。如果我需要添加任何细节,或者需要删除多余的细节,我会尽力快速澄清。
Edit
@0__ 替换了replaceSiteWithIntersection
使用无需编译的方法asInstanceOf
。不幸的是,我找不到一种在没有类型错误的情况下调用该方法的方法。他的代码本质上是这个新类中的第一个方法MonomerClass
;我添加了调用它的第二个方法。
case class MonomerClass[+StateSiteType <: StateSite/* : Manifest*/](
stateSites: Seq[StateSiteType]) {
type MyType = MonomerClass[StateSiteType]
//def manifest = implicitly[Manifest[_ <: StateSiteType]]
def replaceSiteWithIntersection[A <: ReactantStateSite { type MyType = A }]
(thisSite: A, otherMonomer: ReactantMonomer)
(implicit ev: this.type <:< MonomerClass[A])
: MonomerClass[A] = {
val new_this = ev(this)
otherMonomer.stateSites.find(_.name == thisSite.name) match {
case Some(otherSite) =>
val newSites = new_this.stateSites map {
case `thisSite` => thisSite.createIntersection(otherSite)
case other => other
}
copy(stateSites = newSites)
case None => new_this // This throws an exception in the real program
}
}
// Example method that calls the previous method
def replaceSomeSiteOnThisOtherMonomer(otherMonomer: ReactantMonomer)
(implicit ev: MyType <:< ReactantMonomer): MyType = {
// Find a state that is a current member of this.stateSites
// Obviously, a more sophisticated means of selection is actually used
val thisSite = ev(this).stateSites(0)
// I can't get this to compile even with asInstanceOf
replaceSiteWithIntersection(thisSite, otherMonomer)
}
}