在Scala中是否可以强制调用者为多态方法指定类型参数?

时间:2010-12-09 22:47:10

标签: generics scala type-inference

//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;为此: - )

想法?

4 个答案:

答案 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]的对象时才能调用它。当然,defaultoverrideDefault方法可以轻松地为任何类型T创建此类对象。由于这些方法是隐式的,编译器会自动处理调用其中一个方法并将结果传递给find的业务。

但编译器如何知道要调用哪个方法?它使用其类型推断和隐式解析规则来确定适当的方法。有三种情况需要考虑:

    调用
  1. find时没有类型参数。在这种情况下,必须推断类型T。搜索可以提供类型为DefaultsTo[T, Node]的对象的隐式方法时,编译器会找到defaultoverrideDefault。选择default因为它具有优先级(因为它在定义overrideDefault的特征的适当子类中定义)。因此,T必须绑定到Node

  2. 使用非find类型参数(例如Node)调用
  3. find[MyObj]("name")。在这种情况下,必须提供类型为DefaultsTo[MyObj, Node]的对象。只有overrideDefault方法可以提供它,因此编译器会插入适当的调用。

  4. 使用find作为类型参数调用
  5. 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)

有可能得到你想要的东西,但这并不简单。问题是如果没有显式类型参数,编译器只能推断TNothing。在这种情况下,您希望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参数。如果TNothing,则编译器将提供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)。但这是一个相当可怕的解决方案(保罗明确表示他不推荐它),因为它要求你列举所有的子类型。

相关问题