//API
class Node
class Person extends Node
object Finder
{
def find[T <: Node](name: String): T = doFind(name).asInstanceOf[T]
}
//Call site (correct)
val person = find[Person]("joe")
//Call site (dies with a ClassCast inside b/c inferred type is Nothing)
val person = find("joe")
在上面的代码中,客户端站点“忘记”指定类型参数,因为我想要的API编写器意味着“只返回节点”。有没有办法定义一个通用方法(而不是一个类)来实现这个(或等价)。注意:在实现中使用清单来执行转换if(manifest!= scala.reflect.Manifest.Nothing)将无法编译...我有一种唠叨的感觉,一些Scala向导知道如何使用Predef。&lt;: &LT;为此: - )
想法?
答案 0 :(得分:12)
另一种解决方案是为参数指定默认类型,如下所示:
object Finder {
def find[T <: Node](name: String)(implicit e: T DefaultsTo Node): T =
doFind(name).asInstanceOf[T]
}
关键是定义以下幻像类型作为默认的见证:
sealed class DefaultsTo[A, B]
trait LowPriorityDefaultsTo {
implicit def overrideDefault[A,B] = new DefaultsTo[A,B]
}
object DefaultsTo extends LowPriorityDefaultsTo {
implicit def default[B] = new DefaultsTo[B, B]
}
这种方法的优点是它完全避免了错误(在运行时和编译时)。如果调用者未指定type参数,则默认为Node
。
<强>解释强>:
find
方法的签名确保只有在调用者可以提供类型为DefaultsTo[T, Node]
的对象时才能调用它。当然,default
和overrideDefault
方法可以轻松地为任何类型T
创建此类对象。由于这些方法是隐式的,编译器会自动处理调用其中一个方法并将结果传递给find
的业务。
但编译器如何知道要调用哪个方法?它使用其类型推断和隐式解析规则来确定适当的方法。有三种情况需要考虑:
find
时没有类型参数。在这种情况下,必须推断类型T
。搜索可以提供类型为DefaultsTo[T, Node]
的对象的隐式方法时,编译器会找到default
和overrideDefault
。选择default
因为它具有优先级(因为它在定义overrideDefault
的特征的适当子类中定义)。因此,T
必须绑定到Node
。
find
类型参数(例如Node
)调用 find[MyObj]("name")
。在这种情况下,必须提供类型为DefaultsTo[MyObj, Node]
的对象。只有overrideDefault
方法可以提供它,因此编译器会插入适当的调用。
find
作为类型参数调用 Node
。同样,任何一种方法都适用,但default
因其更高的优先级而获胜。
答案 1 :(得分:6)
Miles Sabin posted a really nice solution解决了scala-user邮件列表中的这个问题。按如下方式定义NotNothing
类型类:
sealed trait NotNothing[T] { type U }
object NotNothing {
implicit val nothingIsNothing = new NotNothing[Nothing] { type U = Any }
implicit def notNothing[T] = new NotNothing[T] { type U = T }
}
现在您可以将Finder
定义为
object Finder {
def find[T <: Node : NotNothing](name: String): T =
doFind(name).asInstanceOf[T]
}
如果尝试在没有类型参数的情况下调用Finder.find
,则会出现编译时错误:
错误:含糊不清的隐含值: 两个方法notNothing in object $ iw of type [T] java.lang.Object with NotNothing [T] {type U = T} 并且在对象$ iw中对type =&gt;值noNsNothing带有NotNothing的java.lang.Object [Nothing] {type U = Any} 匹配预期类型NotNothing [T] Finder.find( “乔”)
这个解决方案比我在其他答案中提出的解决方案更为通用。我能看到的唯一缺点是编译时错误非常不透明,@implicitNotFound
注释没有帮助。
答案 2 :(得分:5)
有可能得到你想要的东西,但这并不简单。问题是如果没有显式类型参数,编译器只能推断T
是Nothing
。在这种情况下,您希望find
返回Node
类型的某些内容,{em>不类型T
(即Nothing
),但在其他所有内容中如果您希望查找返回T
类型的内容。
当您希望返回类型根据类型参数而变化时,您可以使用与我在method lifting API中使用的技术类似的技术。
object Finder {
def find[T <: Node] = new Find[T]
class Find[T <: Node] {
def apply[R](name: String)(implicit e: T ReturnAs R): R =
doFind(name).asInstanceOf[R]
}
sealed class ReturnAs[T, R]
trait DefaultReturns {
implicit def returnAs[T] = new ReturnAs[T, T]
}
object ReturnAs extends DefaultReturns {
implicit object returnNothingAsNode extends ReturnAs[Nothing, Node]
}
}
这里,find
方法返回一个多态仿函数,当应用于名称时,将返回类型为T
或类型为Node
的对象,具体取决于编译器提供的ReturnAs
参数。如果T
为Nothing
,则编译器将提供returnNothingAsNode
对象,apply方法将返回Node
。否则,编译器将提供ReturnAs[T, T]
,apply方法将返回T
。
在邮件列表上重复保罗的解决方案,另一种可能性是为“工作”的每种类型提供隐含的。当省略type参数时,不会返回Node
,而是发出编译错误:
object Finder {
def find[T : IsOk](name: String): T =
doFind(name).asInstanceOf[T]
class IsOk[T]
object IsOk {
implicit object personIsOk extends IsOk[Person]
implicit object nodeIsOk extends IsOk[Node]
}
}
当然,这种解决方案不能很好地扩展。
答案 3 :(得分:1)
Paul的解决方案提供了T的下限,因此val person = find(“joe”)是编译时错误,迫使您明确说明类型(例如,Node)。但这是一个相当可怕的解决方案(保罗明确表示他不推荐它),因为它要求你列举所有的子类型。