scala:如何安全地接受返回[未指定的泛型类型参数]的函数-->什么都没有?



我正在处理一个第三方库(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
}

最新更新