Scala:构建一个复杂的特征和类的层次结构
我最近针对
Scala
traits,representation types,member types,manifests和
implicit evidence发布了关于SO的几个问题.在这些问题背后,我的项目是为生物蛋白质网络建立建模软件.尽管有非常有用的答案,这让我比我自己更接近,但我还没有为我的项目找到解决方案.几个答案表明我的设计是有缺陷的,这就是为什么Foo框架问题的解决方案在实践中不起作用.在这里我发布一个更复杂(但仍然大大简化)版本的我的问题.我的希望是,问题和解决方案对于在Scala中建立复杂的性格和阶级等级制度的人来说将是非常有用的.
我的项目最高级别的课程是生物反应规则.一个规则描述了一种或两种反应物如何被反应转化.每个反应物是具有称为单体和边缘的节点的图,其连接在单体上的命名位点之间.每个站点也有一个状态,它可以在.编辑:边缘的概念已经从示例代码中删除,因为它们使实例复杂化,而不会对问题做出太多贡献.规则可以这样说:有一个反应物由分别通过位点a1和b1与单体B结合的单体A制成,债券被违规排除,a1和b1不绑定;同时在单体A上,站点a1的状态从U改为P.我将其写为: A(a1~U-1).B(b1-1) -> A(a1~P) + B(b1) (在Scala中解析这样的字符串非常容易,它使我的头旋转.)-1表示键#1在这些站点之间 – 数字只是一个任意标签. 这是我到目前为止,以及为什么我添加每个组件的推理.它编译,但只有无偿使用asInstanceOf.如何摆脱asInstanceOfs以使类型匹配? 我代表一个基本类的规则: 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类的类具有类型参数,因为我可以对特定图形中允许的单体和边界进行限制;例如,在规则的反应者中不能有任何生成的单体.我还想收集所有类型的单体,反应物单体.我使用类型别名来管理约束. 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.此外,我需要收集特定类型的所有单体,例如,在产品中的规则中收集所有单体,因此我向每个类型参数添加一个清单. 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的实现没有类型参数;它是特征的标准层次结构,终止于具有代表适当状态的名称和一些字符串的类. (关于使用字符串来保存对象状态,他们实际上是我的真实代码中的名称类).这些特征的一个重要目的是提供所有子类需要的功能.那么,不是所有特质的目的.我的特质是特别的,因为许多方法对对象的属性进行了小的更改,该属性对于trait的所有子类都是通用的,然后返回一个副本.如果返回类型与对象的底层类型相匹配,则是最好的.这样做的跛脚方法是使所有的trait方法抽象,并将所需的方法复制到所有的子类中.我不确定正确的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框架的方法.许多方法对类的特定成员进行了一些复杂的搜索,然后将这些成员传递给其他函数,对它们进行了复杂的更改并返回一个副本,然后将其替换为较高级对象的副本.我应该如何参数化方法(或类),以便调用类型安全?现在我可以得到代码,只有很多的asInstanceOfs无处不在. Scala对于传递类型或成员参数的实例特别不满意,因为我可以看到的两个主要原因:(1)协变类型参数最终作为将它们作为输入的任何方法的输入,(2)它是难以说服Scala,返回一个副本的方法确实返回一个与输入的类型完全相同的对象. 我毫无疑问地留下了一些对每个人都不清楚的事情.如果有任何细节,我需要添加,或多余的细节,我需要删除,我会尽快快速清除. 编辑 @ 0__用一个没有asInstanceOf编译的方法代替replaceSiteWithIntersection.不幸的是,我找不到一种没有类型错误的方法来调用该方法.他的代码本质上是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) } } 解决方法
我已经把你的问题减少到了特质,我开始明白你为什么会遇到一些演员和抽象类型的麻烦.
您实际缺少的是ad-hoc多态,您可以通过以下方式获得: 现在我们来看看这个问题.首先是您的方法的签名错误有两个原因: >替换一个站点时,要创建新的通用类型的新单体,就像在添加到集合中时所做的一样,它是一个现有通用类型的超类的对象:你得到一个新的集合,其类型参数是超类.因此,您应该产生这种新的单体. def replaceSiteWithIntersection[A >: StateSiteType <: ReactantStateSite] (thisSite: A,monomer: ReactantMonomer): Option[MonomerClass[A]] 如果我们现在看到digger在类型错误,我们可以看到真正的类型错误来自于这种方法: thisSite.createIntersection 原因很简单:它的签名与其余的类型不一致,因为它接受一个ReactantSite,但是您希望将其称为您的stateSites(其中Seq [StateSiteType]类型)的参数,但您无法保证那 StateSiteType<:<ReactantSite 现在我们来看看证据如何帮助你: trait Intersector[T] { def apply(observed: Seq[String]): T } trait StateSite { def name: String } trait ReactantStateSite extends StateSite { def observed: Seq[String] def createIntersection[A](otherSite: ReactantStateSite)(implicit intersector: Intersector[A]): A = { val newStates = observed.intersect(otherSite.observed) intersector(newStates) } } import Monomers._ trait MonomerClass[+StateSiteType <: StateSite] { val stateSites: Seq[StateSiteType] def replaceSiteWithIntersection[A >: StateSiteType <: ReactantStateSite](thisSite: A,otherMonomer: ReactantMonomer)(implicit intersector:Intersector[A],ev: StateSiteType <:< ReactantStateSite): Option[MonomerClass[A]] = { def replaceOrKeep(condition: (StateSiteType) => Boolean)(f: (StateSiteType) => A)(implicit ev: StateSiteType<:<A): Seq[A] = { stateSites.map { site => if (condition(site)) f(site) else site } } val reactantSiteToIntersect:Option[ReactantStateSite] = otherMonomer.stateSites.find(_.name == thisSite.name) reactantSiteToIntersect.map { siteToReplace => val newSites = replaceOrKeep {_ == thisSite } { item => thisSite.createIntersection( ev(item) ) } MonomerClass(newSites) } } } object MonomerClass { def apply[A <: StateSite](sites:Seq[A]):MonomerClass[A] = new MonomerClass[A] { val stateSites = sites } } object Monomers{ type Monomer = MonomerClass[StateSite] type ReactantMonomer = MonomerClass[ReactantStateSite] type ProductMonomer = MonomerClass[ProductStateSite] type ProducedMonomer = MonomerClass[ProducedStateSite] } >请注意,如果您以巧妙的方式使用隐式解析规则(例如,将您的Insector放在Intersector trait的伴随对象中,以便自动解析),则此模式可以无需特殊导入. 您将不得不使您的MonomerClass不变,这可能是一个问题(但为什么需要协方差)? trait CanReact[A,B] { implicit val intersector: Intersector[B] def react(a: A,b: B): B def reactFunction(b:B) : A=>B = react(_:A,b) } object CanReact { implicit def CanReactWithReactantSite[A<:ReactantStateSite](implicit inters: Intersector[A]): CanReact[ReactantStateSite,A] = { new CanReact[ReactantStateSite,A] { val intersector = inters def react(a: ReactantStateSite,b: A) = a.createIntersection(b) } } } trait MonomerClass[StateSiteType <: StateSite] { val stateSites: Seq[StateSiteType] def replaceSiteWithIntersection[A >: StateSiteType <: ReactantStateSite](thisSite: A,otherMonomer: ReactantMonomer)(implicit canReact:CanReact[StateSiteType,A]): Option[MonomerClass[A]] = { def replaceOrKeep(condition: (StateSiteType) => Boolean)(f: (StateSiteType) => A)(implicit ev: StateSiteType<:<A): Seq[A] = { stateSites.map { site => if (condition(site)) f(site) else site } } val reactantSiteToIntersect:Option[ReactantStateSite] = otherMonomer.stateSites.find(_.name == thisSite.name) reactantSiteToIntersect.map { siteToReplace => val newSites = replaceOrKeep {_ == thisSite } { canReact.reactFunction(thisSite)} MonomerClass(newSites) } } } 有了这样的实现,只要您想要将站点替换为其他不同类型的站点,您所需要的就是使用不同类型的CanReact新隐藏实例. 我会结束一个(我希望)明确的解释为什么你不应该需要协方差. 假设你有一个消费者[T]和一个生产者[T]. 当您想要向消费者[T1]提供T2<:< T1的生产者[T2]时,需要协方差.但是,如果您需要使用T1内T1产生的值,可以 class ConsumerOfStuff[T <: CanBeContained] { def doWith(stuff: Stuff[T]) = stuff.t.writeSomething } trait CanBeContained { def writeSomething: Unit } class A extends CanBeContained { def writeSomething = println("hello") } class B extends A { override def writeSomething = println("goodbye") } class Stuff[T <: CanBeContained](val t: T) object VarianceTest { val stuff1 = new Stuff(new A) val stuff2 = new Stuff(new B) val consumerOfStuff = new ConsumerOfStuff[A] consumerOfStuff.doWith(stuff2) } 这些东西显然没有编译:
但是,再次,这是来自对方差使用的误解,正如How are co- and contra-variance used in designing business applications? Kris Nuttycombe的答案解释一样.如果我们重构如下 class ConsumerOfStuff[T <: CanBeContained] { def doWith[A<:T](stuff: Stuff[A]) = stuff.t.writeSomething } 你可以看到一切都很好. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |