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

Ale*_*ets 14 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")
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,客户端站点"忘记"指定类型参数,因为API编写器我希望它意味着"只返回节点".有没有办法定义一个通用方法(而不是一个类)来实现这个(或等价).注意:在实现中使用清单来执行转换if(manifest!= scala.reflect.Manifest.Nothing)将无法编译...我有一种唠叨的感觉,一些Scala向导知道如何使用Predef.<:<为了这 :-)

想法?

Aar*_*rup 12

另一种解决方案是为参数指定默认类型,如下所示:

object Finder {
   def find[T <: Node](name: String)(implicit e: T DefaultsTo Node): T = 
      doFind(name).asInstanceOf[T]
}
Run Code Online (Sandbox Code Playgroud)

关键是要定义以下幻像类型作为默认的见证:

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]
}
Run Code Online (Sandbox Code Playgroud)

这种方法的优点是它完全避免了错误(在运行时和编译时).如果调用者未指定type参数,则默认为Node.

说明:

find方法的签名确保只有在调用者可以提供类型的对象时才能调用它DefaultsTo[T, Node].当然,defaultoverrideDefault方法可以很容易地为任何类型创建这样的对象T.由于这些方法是隐式的,编译器会自动处理调用其中一个并将结果传递给的业务find.

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

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

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

  3. find被调用,Node作为类型参数.同样,任何一种方法都适用,但default由于其更高的优先级而获胜.


Aar*_*rup 6

Miles Sabin 在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 }           
}
Run Code Online (Sandbox Code Playgroud)

现在你可以定义你Finder

object Finder {
   def find[T <: Node : NotNothing](name: String): T = 
      doFind(name).asInstanceOf[T]
}
Run Code Online (Sandbox Code Playgroud)

如果尝试在Finder.find没有类型参数的情况下调用,则会出现编译时错误:

错误:含糊不清的隐式值:两个方法notNothing in object $ iw of type [T] java.lang.Object with NotNothing [T] {type U = T},并且在对象$ iw中输入noIsNothing类型=> java.lang.Object with NotNothing [Nothing] {type U = Any}匹配预期类型NotNothing [T] Finder.find("joe")

这个解决方案比我在其他答案中提出的解决方案更为通用.我能看到的唯一缺点是编译时错误是非常不透明的,并且@implicitNotFound注释没有帮助.


Aar*_*rup 5

你可以得到你想要的东西,但这并不简单.问题是没有显式类型参数,编译器只能推断出它TNothing.在这种情况下,您希望find返回类型的东西Node,而不是类型T(即Nothing),但在其他每种情况下,您希望找到返回类型的东西T.

如果希望返回类型根据类型参数而变化,则可以使用类似于我在方法提升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]
   }
}
Run Code Online (Sandbox Code Playgroud)

这里,该find方法返回一个多态仿函数,当应用于名称时,它将返回一个类型T或类型的对象,Node具体取决于ReturnAs编译器提供的参数的值.如果TNothing,编译器将提供returnNothingAsNode对象,apply方法将返回一个Node.否则,编译器将提供一个ReturnAs[T, T],而apply方法将返回一个T.


在邮件列表中重复保罗的解决方案,另一种可能性是为"工作"的每种类型提供隐含的.而不是在Node省略type参数时返回,将发出编译错误:

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]
   }
}
Run Code Online (Sandbox Code Playgroud)

当然,这种解决方案不能很好地扩展.