Scala:执行A不是B的子类型

Kva*_*ass 7 scala

我试图根据参数是否扩展给定的类来重载方法,并且遇到了一些麻烦.使用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
}
Run Code Online (Sandbox Code Playgroud)

而我的用例:

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

不幸的是,这会导致歧义,编译器不知道要调用哪两种方法.我正在寻找一个解释,为什么在这种情况下存在歧义(与Miles的要点中概述的其他更简单的情况相反)以及如何规避这个障碍.请注意,我需要在参数级别上执行此检查(而不是定义一个方法并在正文中执行检查),因为我想要具有不同的返回类型.

Tra*_*own 7

第一个问题是,由于你在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))
                      ^
Run Code Online (Sandbox Code Playgroud)

(请注意,我缩写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])
              ^
Run Code Online (Sandbox Code Playgroud)

所以这里发生的是编译器不会让第二个参数列表决定使用哪个重载定义.这似乎意味着具有多个参数列表的重载方法(其中第一个参数列表相同)基本上是无用的.可能有一张这样的票 - 一眼就能看出我最接近的是SI-2383.

然而,这些都不重要,因为你不应该在这里使用重载方法 - 重载是一个可怕的"特性",它是Java的宿醉并打破了各种各样的东西.

但是,还有其他可能的方法.我最喜欢的一些奇怪的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)
Run Code Online (Sandbox Code Playgroud)

然后:

scala> foobar(Bar(13))
res0: Either[Int,String] = Right(hello)

scala> foobar(Baz(13))
res1: Either[Int,String] = Left(5)
Run Code Online (Sandbox Code Playgroud)

请注意,我使用的是Either而不是让隐式存在确定返回类型.有一些方法可以实现这一点(比如Shapeless一级多态函数值),但在这种情况下它们可能有点过分.


更新:好的,因为你要求它:

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

然后:

scala> foobar(Bar(13))
res6: String = hello

scala> foobar(Baz(13))
res7: Int = 5
Run Code Online (Sandbox Code Playgroud)

没有包装.不过,在采用这种方法之前,你应该认真思考.


更新到更新,为了完整起见:如果没有使用依赖方法类型的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)
Run Code Online (Sandbox Code Playgroud)

然后:

scala> foobar(Bar(13))
res0: String = hello

scala> foobar(Baz(13))
res1: Int = 5
Run Code Online (Sandbox Code Playgroud)

同样,这是相当先进的东西,应谨慎使用.

  • WRT对更新的更新...这是"只使用类型类,该死的"方法,我认为这在这里是完全合适的. (2认同)