如何在没有歧义的情况下用不同的证据重载泛型方法?

How to overload generic method with different evidence without ambiguity?

我有一个带有两个参数的自定义 compare 方法。其中一个应该可以隐式转换为另一个:

object Test extends App {
  def compare[T1, T2](a: T1, b: T2)(implicit ev: T1 => T2) = compareImpl[T2](ev(a), b)
  def compare[T1, T2](a: T1, b: T2)(implicit ev: T2 => T1) = compareImpl[T1](a, ev(b))

  def compareImpl[T](a: T, b: T) = a == b

  case class Foo(s: String)
  case class Bar(s: String)

  implicit def foo2bar(f: Foo): Bar = Bar(f.s)

  println(compare(Foo("hello"), Bar("hello")))

}

但是这个片段给我错误:

error: ambiguous reference to overloaded definition,
       both method compare in object Test of type [T1, T2](a: T1, b: T2)(implicit ev: T2 => T1)Boolean
       and  method compare in object Test of type [T1, T2](a: T1, b: T2)(implicit ev: T1 => T2)Boolean
       match argument types (Test.Foo,Test.Bar) and expected result type Any
         implicit def foo2bar(f: Foo): Bar = Bar(f.s)

如果我删除第二个 compare,它可以工作,但是如果我删除 compare(Bar("hello), Foo("hello")),它就无法编译。

这两个版本怎么才能不歧义呢?

这里的问题是因为你的两个 compare 函数都具有完全相同的类型参数,这对于 Scala 编译器来说是不明确的,无法确定使用哪个。

例如,当您比较 compare[Foo, Bar] 时,Scala 编译器不清楚它应该将 compare 函数与 (implicit ev: T1 => T2) 一起使用,还是第二个函数与 [=14] 一起使用=] ,因为 FooBar 都可以放置为 T1T2

事实上,这就是当您删除其中一个比较功能时它起作用的原因。因为 compare 函数没有重载版本,所以 FooBar 可以作为 T1T2 放置在你唯一的 compare 中功能。

这是另一个 Whosebug 问题的答案,该问题与您的问题有某种关联,并详细描述了问题:

我最终使用了宏,因为目前 Scala 没有类型 lambda 并且它执行泛型类型擦除,所以开箱即用不支持这样的东西。

宏定义:

import scala.reflect.runtime.universe._
import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros
import scala.language.implicitConversions

def compare[T1, T2](a: T1, b: T2): Boolean = macro compareImpl[T1,T2]

def compareImpl[T1: c.WeakTypeTag, T2: c.WeakTypeTag](c: Context)(a: c.Expr[T1], b: c.Expr[T2]): c.Expr[Boolean] = {
  import c.universe._
  // Search for T1=>T2 first. If not found, search T2=>T1
  val f1 = c.inferImplicitValue(c.weakTypeOf[T1 => T2])
  if (f1.isEmpty) {
      val f2 = c.inferImplicitValue(c.weakTypeOf[T2 => T1])
      if(f2.isEmpty) {
          c.abort(c.enclosingPosition, s"Cannot find ${weakTypeOf[T1]}=> ${weakTypeOf[T2]}")
      }
      else {
          c.Expr(q"$f2.apply($b) == $a")
      }
  }
  else {
      c.Expr(q"$f1.apply($a) == $b")
  }
}

测试:

case class Foo(s: String)
case class Bar(s: String)

implicit def foo2bar(f: Foo): Bar = Bar(f.s)

println(compare(Foo("hello"), Bar("hello")))
println(compare(Bar("hello"), Foo("hello")))

没有宏的解决方案(它基于类型类)

  def compare[T1, T2](a: T1, b: T2)(implicit cmp: Compare[T1, T2]) = (compareImpl[cmp.T] _).tupled(cmp(a, b))
  def compareImpl[T](a: T, b: T) = a == b

  trait Compare[T1, T2] {
    type T
    type Out = (T, T)
    def apply(a: T1, b: T2): Out
  }

  object Compare {
    type Aux[T1, T2, T0] = Compare[T1, T2] { type T = T0 }
    def instance[T1, T2, T0](f: (T1, T2) => (T0, T0)): Aux[T1, T2, T0] = new Compare[T1, T2] {
      override type T = T0
      override def apply(a: T1, b: T2): Out = f(a, b)
    }

    implicit def directCompare[T1, T2](implicit ev: T1 => T2): Aux[T1, T2, T2] = instance((a, b) => (ev(a), b))
    implicit def reverseCompare[T1, T2](implicit ev: T2 => T1): Aux[T1, T2, T1] = instance((a, b) => (a, ev(b)))
  }

  case class Foo(s: String)
  case class Bar(s: String)

  implicit def foo2bar(f: Foo): Bar = Bar(f.s)

  println(compare(Foo("hello"), Bar("hello"))) // true

或者,如果需要,您甚至可以优先考虑正向和反向方向

  def compare[T1, T2](a: T1, b: T2)(implicit cmp: Compare[T1, T2]) = (compareImpl[cmp.T] _).tupled(cmp(a, b))
  def compareImpl[T](a: T, b: T) = a == b

  trait Compare[T1, T2] {
    type T
    type Out = (T, T)
    def apply(a: T1, b: T2): Out
  }

  trait LowPriorityCompare {
    type Aux[T1, T2, T0] = Compare[T1, T2] { type T = T0 }
    def instance[T1, T2, T0](f: (T1, T2) => (T0, T0)): Aux[T1, T2, T0] = new Compare[T1, T2] {
      override type T = T0
      override def apply(a: T1, b: T2): Out = f(a, b)
    }

    implicit def reverseCompare[T1, T2](implicit ev: T2 => T1): Aux[T1, T2, T1] = instance((a, b) => (a, ev(b)))
  }

  object Compare extends LowPriorityCompare {
    implicit def directCompare[T1, T2](implicit ev: T1 => T2): Aux[T1, T2, T2] = instance((a, b) => (ev(a), b))
  }

  case class Foo(s: String)
  case class Bar(s: String)

  implicit def foo2bar(f: Foo): Bar = Bar(f.s)
  implicit def bar2foo(f: Bar): Foo = Foo(f.s)

  println(compare(Foo("hello"), Bar("hello"))) // true