2013-03-11 113 views
3

我正在開發一個ORM風格的庫在ReactiveMongo之上。目前我試圖實現一個嵌套的文檔表示 - 但是我一直堅持Scala的類型推理爲我的課程。我對Scala相當陌生,所以每一個幫助都是值得歡迎的。Scala類型推斷限制

這是我的嘗試:

trait MongoDocument[T <: MongoDocument[T]] { 
    self: T => 

    val db: DefaultDB 
    val collection: String 

    var fields: List[MongoField[T,_]] = List.empty 
    def apply(doc: TraversableBSONDocument): T = { // loads the content of supplied document 
    fields.foreach { field => 
     doc.get(field.field).foreach(field.load(_)) 
    } 
    this 
    } 
} 
trait MongoMetaDocument[T <: MongoDocument[T]] extends MongoDocument[T] { 
    self: T => 

    type DocType = T 

    protected val clazz = this.getClass.getSuperclass 
    def create: T = clazz.newInstance().asInstanceOf[T] 

    def find(id: String): Future[Option[T]] = { 
    db(collection).find(BSONDocument("_id" -> BSONObjectID(id))) 
     .headOption().map(a => a.map(create.apply(_))) 
    } 
} 

abstract class MongoField[Doc <: MongoDocument[Doc], T](doc: MongoDocument[Doc], val field: String) { 
    var value: Option[T] = None 

    def is: Option[T] = value 
    def apply(in: T) { value = Some(in) } 
    def unset() { value = None } 

    def load(bson: BSONValue) 

    doc.fields ::= this 
} 

class MongoInteger[Doc <: MongoDocument[Doc]](doc: Doc, field: String) 
    extends MongoField[Doc, Int](doc, field) { 

    def load(bson: BSONValue) { value = Try(bson.asInstanceOf[BSONNumberLike].toInt).toOption } 
} 

class MongoDoc[Doc <: MongoDocument[Doc], SubDocF <: MongoMetaDocument[SubDoc], SubDoc <: MongoDocument[SubDoc]](doc: Doc, field: String, meta: SubDocF) 
    extends MongoField[Doc, SubDocF#DocType](doc, field) { 

    def load(bson: BSONValue) { 
    value = Try(bson.asInstanceOf[TraversableBSONDocument]).toOption.map { doc => 
     meta.create.apply(doc) 
    } 
    } 
} 

假設我有以下代碼:

class SubEntity extends MongoDocument[SubEntity] { 
    val db = Db.get 
    val collection = "" 

    val field = new MongoInteger(this, "field") 
} 
object SubEntity extends SubEntity with MongoMetaDocument[SubEntity] 

我還想寫另一個實體爲:

class Another extends MongoDocument[Another] { 
    val db = Db.get 
    val collection = "test" 

    val subEntity = new MongoDoc(this, "subEntity", SubEntity) 
} 
object Another extends Another with MongoMetaDocument[Another] 

Db.get只是返回一個DefaultDB

但是斯卡拉不能推斷該類型的MongoDoc例如,即使我想他們可能是inferrable(如Doc很容易推斷出,SubDocF可以推斷SubEntity.typeSubEntity混入只是MongoMetaDocument[SubEntity]那麼SubDoc類型必須是SubEntity) 。如果我使用下面的代碼,一切都很好:

val subEntity = new MongoDoc[Another,SubEntity.type,SubEntity](this, "subEntity", SubEntity) 

有沒有一些解決方案,類型不需要明確設置?由於我需要構建一個擴展自MongoDocument特徵的類的實例,我試圖通過使用具有create方法的元對象來解決這個問題。

目前我唯一提出的解決方法是使用implicitly,但這會使實體定義更加噁心。

謝謝你幫我理清了這一點(或者給我一些提示如何安排我的類層次結構,這將不會是一個問題)

回答

2

,如果你擺脫你會得到更好的類型推斷的「F-界多態性」。 (也就是當你發現你自己寫了「T <:Foo [T]」形式的類型參數),而是在具體類中指定的特徵中使用抽象類型成員。

你會發現,推斷在這裏工作:

trait MongoDocument 
    type DocType <: MongoDocument 

    def apply(doc: TraversableBSONDocument): DocType = ??? // loads the content of supplied document 
} 
trait MongoMetaDocument extends MongoDocument { 

    protected val clazz = this.getClass.getSuperclass 
    def create: DocType = clazz.newInstance().asInstanceOf[DocType] 

    def find(id: String): Future[Option[DocType]] = ??? 
} 

abstract class MongoField[Doc <: MongoDocument, T](doc: MongoDocument, field: String) { 
    type DocType <: MongoDocument 
    var value: Option[T] 

    def is: Option[T] = value 
    def apply(in: T) { value = Some(in) } 
    def unset() { value = None } 

    def load(bson: BSONValue) 

} 

class MongoDoc[Doc <: MongoDocument, SubDocF <: MongoMetaDocument, SubDoc <: MongoDocument](doc: Doc, field: String, meta: SubDocF) 
    extends MongoField[Doc, SubDocF#DocType](doc, field) { 
    type DocType = Doc 

    def load(bson: BSONValue) { 
    value = Try(bson.asInstanceOf[TraversableBSONDocument]).toOption.map { doc => 
     ??? 
    } 
    } 
} 

class SubEntity extends MongoDocument { 
    type DocType = SubEntity 
    ??? 
} 
object SubEntity extends SubEntity with MongoMetaDocument 

class Another extends MongoDocument { 
    type DocType = Another 
    val subEntity = new MongoDoc(this, "subEntity", SubEntity) 
} 
object Another extends Another with MongoMetaDocument 
+0

我試圖根據你的回答來重寫我的實現,我已經在不同的問題上運行。對'MongoDocument#DocType'的類型沒有限制,因此庫的用戶可以用'DocType = SubEntity'指定實體'Another'(導致至少在運行時出現運行時異常)。而且'MongoDocument.apply'方法試圖可鏈接並返回,因此'this'不能編譯爲'this',只是'MongoDocument'類型。 (以及「this'不是'MongoDocument#Doctype'的實例的缺失假設也會在不同的地方破壞我的代碼) – 2013-03-12 09:02:37

+0

我已更改我的問題中的代碼,以便更明顯地發現問題出現的位置。 (並且感謝您的回答!) – 2013-03-12 09:28:01

+0

DocType存在與您的示例中相同的限制。它必須是MongoDocument的子類型。在你的情況下,你認爲在這種情況下,你知道這個提示是另一個必須被指定爲Another擴展MongoDocument [Another],但是另一個擴展MongoDocument [SubEntity]仍然允許在你的例子中,是的,無論如何,我們允許人們編寫惡意類,但是沒有一種好的方法來防止這種類,並且仍然以類型安全的方式獲得您希望的返回類型多態。 – stew 2013-03-12 13:14:03