我正在处理一个第三方库(orientDB),它有大量的API形式:
trait MyTrait {
def method[RET](): RET
}
在许多情况下,调用MyTrait的实例而不指定RET参数是明智的。
例如:我可能调用document.save(db)。这里我调用了save[T=undef],scala决定结果的类型为Nothing。在许多情况下,这并不是什么大事。
然而,考虑一下这样的东西:
class DatabaseConnection
trait Saveable {
def save[RET](db:DatabaseConnection): RET
}
class Document extends Saveable {
// Let's assume only RET=Document is valid
def save[RET](db:DatabaseConnection) = this.asInstanceOf[RET]
}
def wrap[T](func: (DatabaseConnection) => T): T = {
val db = new DatabaseConnection
try {
func(db)
} finally {
// Close the database connection. Omitted
}
}
def f(db:DatabaseConnection) : Unit = new Document().save(db)
f( new DatabaseConnection() ) // OK
wrap{ db =>
new Document().save(db) // FAIL. Document cannot be cast to scala.runtime.Nothing
}
第一次调用成功是因为从未使用Nothing结果:f忽略结果并返回unit。第二个调用失败,因为new Document().save(db)承诺了Nothing类型(至少scala是这么决定的)。由此产生的失败有点令人难过,因为包装器是通用的,并且看起来应该能够接受返回任何的函数,包括Nothing。
我发现许多帖子解释了类似的问题(泛型+不能强制转换为Nothing),但没有一篇文章解释如何在不需要wrap的调用者提供类型参数的情况下处理它。当类型边界具有提供它必须是Object/Any的约束时,它甚至会失败。
是否可以修改wrap,使其适用于任何接受DatabaseConnection的函数,即使是那些返回Nothing的函数?我不希望调用者必须完全指定类型参数,因为:I)不必要,ii)非常繁重,iii)不安全——因为调用者可能会忘记。
例如,我可以说我想要一个返回类型为"DontCare"或"NothingOrAny"的函数,而不是指定T吗?约束T=Nothing没有帮助,因为函数实际上返回了一个Document。看起来wrap应该要求函数返回T=Nothing或Any。
更新:
下面是一个类似的失败示例:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx
import com.orientechnologies.orient.core.sql.query.{OResultSet, OSQLSynchQuery}
import com.orientechnologies.orient.core.record.impl.ODocument
def toODocumentValue(node:JsonNode):Any =
node match {
case e: NullNode => null
case e: TextNode => e.asText()
case e: BooleanNode => e.asBoolean()
case e: DoubleNode => e.asDouble()
case e: IntNode => e.asInt()
case e: LongNode => e.asLong()
case e: ArrayNode => e.elements().asScala.map(toODocumentValue(_)).toList.asJava
case e: ObjectNode =>
val doc = new ODocument
e.fields().asScala.foreach(field => doc.field(field.getKey, toODocumentValue(field.getValue)))
doc
}
val JSON_PERSON_RECORD_LIST =
"""
[
{
"gender": {"name": "Male"},
"firstName": "Robert",
"lastName": "Smith",
"account": {"checking": 10, "savings": 1234}
},
{
"gender": {"name": "Male"},
"firstName": "Dan",
"lastName": "Dare",
"account": {"checking": 10, "savings": 1234}
}
]
"""
val db: ODatabaseDocumentTx = val db: ODatabaseDocumentTx = new ODatabaseDocumentTx("memory:jsondb")
db.create()
// Jackson mapper
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
// Read a JSON of format [{obj1},{obj2},...]
// and save objects into DB
val node = mapper.readTree(JSON_PERSON_RECORD_LIST)
// Fails because db.save: Nothing
// Even though the return type is not used, scala checks its type
// at runtime.
node.elements().asScala.foreach{ node => db.save( toODocumentValue(node).asInstanceOf[ODocument] ) }
呼叫者需要写:
// Added ORecord type parameter
node.elements().asScala.foreach{ node => db.save[ORecord](
toODocumentValue(node).asInstanceOf[ODocument] ) }
将scala与ODB Java API一起使用非常困难,因为这种类型的泛型在orientedb中非常丰富,并且直到运行时才发现错误。它真的需要自己的scala API
您的save
方法似乎需要更严格的类型。根据它的名称,我假设它的目的是只保存特征Saveable
的具体子类的成员。
因此,像这样定义save
是有意义的:
trait Saveable {
def save(db:DatabaseConnection): this.type
}
class Document extends Saveable {
def save(db:DatabaseConnection) = this
}