我试图根据参数是否扩展给定类来重载方法,但遇到了一些麻烦。使用 an approach by Miles Sabin ,我生成了以下代码:
object ExtendedGenericTypes {
trait <:!<[A, B] // Encoding for "A is not a subtype of B"
// Use ambiguity to rule out the cases we're trying to exclude
implicit def nsubAmbig1[A, B >: A]: A <:!< B = null
implicit def nsubAmbig2[A, B >: A]: A <:!< B = null
// The implicit substitutions
implicit def nsub[A, B]: A <:!< B = null
}
我的用例:
import ExtendedGenericTypes._
class Foo
def foobar[T](x: T)(implicit ev: T <:< Foo) = "hello"
def foobar[T](x: T)(implicit ev: T <:!< Foo) = 5
println(foobar(new Foo()))
不幸的是,这会导致歧义,并且编译器不知道要调用这两种方法中的哪一种。我正在寻找关于为什么在这种情况下存在歧义的解释(与 Miles 的要点中概述的其他更简单的情况相反)以及如何规避这一障碍。请注意,我需要在参数级别执行此检查(而不是定义一种方法并在主体中进行检查),因为我想要不同的返回类型。
请您参考如下方法:
第一个问题是,由于您在 REPL 中的工作方式,第二个 foobar
只是掩盖了第一个。如果你想要一个重载的定义,你需要使用 :paste
同时定义两者。
这仍然不会让你得到你想要的,只是一个新的错误消息:
scala> println(foobar(new Foo))
<console>:14: error: ambiguous reference to overloaded definition,
both method foobar of type [T](x: T)(implicit ev: EGT.<:!<[T,Foo])Int
and method foobar of type [T](x: T)(implicit ev: <:<[T,Foo])String
match argument types (Foo) and expected result type Any
println(foobar(new Foo))
^
(请注意,我使用了缩写
ExtendedGenericTypes
,因为我讨厌水平滚动条。)
您甚至可以尝试提供
<:<
实例明确:
scala> foobar(new Foo)(implicitly[Foo <:< Foo])
<console>:14: error: ambiguous reference to overloaded definition,
both method foobar of type [T](x: T)(implicit ev: EGT.<:!<[T,Foo])Int
and method foobar of type [T](x: T)(implicit ev: <:<[T,Foo])String
match argument types (Foo)
foobar(new Foo)(implicitly[Foo <:< Foo])
^
所以这里发生的事情是编译器不会让第二个参数列表决定使用哪个重载定义。这似乎意味着具有多个参数列表(其中第一个参数列表相同)的重载方法本质上是无用的。可能有一张票——一目了然,我能想到的最接近的是 SI-2383 .
不过,这些都不重要,因为你不应该在这里使用重载方法——重载是一个可怕的“特性”,它是 Java 和 breaks all kinds of stuff 的宿醉。 .
不过,还有其他可能的方法。我最喜欢的一些奇怪的 Scala 技巧依赖于这样一个事实,即您可以为隐式参数提供默认值,如果编译器找不到实例,则将使用该默认值。如果我理解正确,你想要这样的东西:
class Foo
def foobar[T](x: T)(implicit ev: T <:< Foo = null) =
Option(ev).fold[Either[Int, String]](Left(5))(_ => Right("hello"))
case class Bar(i: Int) extends Foo
case class Baz(i: Int)
进而:
scala> foobar(Bar(13))
res0: Either[Int,String] = Right(hello)
scala> foobar(Baz(13))
res1: Either[Int,String] = Left(5)
请注意,我使用的是
Either
而不是让隐式的存在决定返回类型。有很多方法可以做到这一点(比如
Shapeless 的
first-class polymorphic function values ),但在这种情况下它们可能有点过头了。
更新:好的,因为你要求它:
import shapeless._
trait LowPriorityFoobar { this: Poly1 =>
implicit def anyOld[T] = at[T](_ => 5)
}
object foobar extends Poly1 with LowPriorityFoobar {
implicit def foo[T](implicit ev: T <:< Foo) = at[T](_ => "hello")
}
进而:
scala> foobar(Bar(13))
res6: String = hello
scala> foobar(Baz(13))
res7: Int = 5
没有包装。不过,在采用这种方法之前,您应该认真考虑一下。
更新更新,为了完整起见:您还可以更直接地(但也更详细)执行此操作,而无需使用依赖方法类型的 Shapeless(同样,您需要使用
:paste
一次定义所有这些):
class Foo
trait IsFooMapper[I] {
type Out
def apply(i: I): Out
}
trait LowPriorityIsFooMapper {
implicit def isntFoo[A] = new IsFooMapper[A] {
type Out = Int
def apply(a: A) = 5
}
}
object IsFooMapper extends LowPriorityIsFooMapper {
implicit def isFoo[A](implicit ev: A <:< Foo) = new IsFooMapper[A] {
type Out = String
def apply(a: A) = "hello"
}
}
def foobar[A](a: A)(implicit ifm: IsFooMapper[A]) = ifm(a)
进而:
scala> foobar(Bar(13))
res0: String = hello
scala> foobar(Baz(13))
res1: Int = 5
同样,这是相当高级的东西,应该谨慎使用。