私は最近、Scalaの特性、表現型、メンバー型、マニフェスト、および暗黙の証拠を扱う SO に関するいくつかの質問を投稿しました。これらの疑問の背後には、生物学的タンパク質ネットワークのモデリング ソフトウェアを構築するという私のプロジェクトがあります。非常に役立つ回答が寄せられ、自分自身で得ることができなかったほど身近になりましたが、プロジェクトの解決策にはまだ到達していません。いくつかの回答は、私の設計に欠陥があることを示唆しています。Foo
-フレーム付きの質問は実際には機能しません。ここでは、私の問題のより複雑な (ただし大幅に簡略化された) バージョンを投稿しています。この問題と解決策が、Scala でトレイトとクラスの複雑な階層を構築しようとしている人々にとって広く役立つことを願っています。
私のプロジェクトの最上位クラスは、生物反応規則です。ルールは、1 つまたは 2 つの反応物が反応によってどのように変換されるかを記述します。各反応物は、モノマーと呼ばれるノードと、モノマーの名前付きサイト間を接続するエッジを持つグラフです。各サイトには、可能な状態もあります。編集:エッジの概念は、質問にあまり貢献せずに例を複雑にするため、コード例から削除されました。規則は次のように言うかもしれません: それぞれサイト a1 と b1 を介してモノマー B に結合したモノマー A でできた 1 つの反応物があります。結合は、サイト a1 と b1 を非結合のままにするルールによって切断されます。モノマー A で同時に、サイト a1 の状態が U から P に変更されます。これを次のように記述します。
A(a1~U-1).B(b1-1) -> A(a1~P) + B(b1)
(Scala でこのような文字列を解析するのはとても簡単で、頭が混乱しました。)-1
は、bond #1 がこれらのサイト間にあることを示しています。番号は、任意のラベルにすぎません。
これまでに得たものと、各コンポーネントを追加した理由を以下に示します。コンパイルされますが、無償で使用する必要がありasInstanceOf
ます。asInstanceOf
タイプが一致するように s を削除するにはどうすればよいですか?
基本クラスでルールを表します。
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
は型パラメーターがあります。これにより、特定のグラフで許可されるモノマーとエッジの種類に制約を加えることができます。たとえば、aProducedMonomer
の に s を含めることはできません。また、特定のタイプのすべてのsを実行できるようにしたいと考えています。型エイリアスを使用して制約を管理します。Reactant
Rule
collect
Monomer
ReactantMonomer
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
も型パラメーターがあるため、サイトに制約を加えることができます。たとえば、 a に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
適切な状態を表す です。(オブジェクトの状態を保持するために文字列を使用することに注意してください。実際には、実際のコードでは名前クラスです。) これらのトレイトの重要な目的の 1 つは、すべてのサブクラスが必要とする機能を提供することです。まあ、それがすべての特性の目的ではありません。私の特性は、多くのメソッドが特性のすべてのサブクラスに共通するオブジェクトのプロパティに小さな変更を加えてからコピーを返すという点で特別です。戻り値の型がオブジェクトの基になる型と一致することが望ましいでしょう。これを行うための不十分な方法は、すべてのトレイト メソッドを抽象化し、必要なメソッドをすべてのサブクラスにコピーすることです。これを行うための適切な 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 は、私が見ることができる 2 つの主な理由により、型またはメンバー パラメーターのインスタンスを渡すことに特に不満を持っています。コピーを返すメソッドが実際に入れられたものとまったく同じ型のオブジェクトを返すことを Scala に納得させるのは困難です。
私は間違いなく、誰にとっても明確ではないいくつかのことを残しました. 追加する必要がある詳細、または削除する必要がある余分な詳細がある場合は、迅速に解決できるように努めます。
編集
@0__ は、replaceSiteWithIntersection
を なしでコンパイルしたメソッドに置き換えましたasInstanceOf
。残念ながら、型エラーなしでメソッドを呼び出す方法が見つかりません。彼のコードは基本的に、この新しいクラスの最初のメソッドですMonomerClass
。それを呼び出す 2 番目のメソッドを追加しました。
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)
}
}