处理隐式类型类冲突 [英] Dealing with implicit typeclass conflict

查看:58
本文介绍了处理隐式类型类冲突的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试解决一个模棱两可的隐式问题,并(相关地)弄清楚应采用哪种最佳实践来对类型类进行参数化.我遇到一种情况,我正在使用类型类来实现多态方法.我最初尝试了以下方法:

I'm trying to deal with an ambiguous implicits problem, and (relatedly) figure out what best practise should be for parameterizing typeclasses. I have a situation where I am using a typeclass to implement a polymorphic method. I initially tried the approach below:

abstract class IsValidTypeForContainer[A]
object IsValidTypeForContainer {
  implicit val IntIsValid = new IsValidTypeForContainer[Int] {}
  implicit val DoubleIsValid = new IsValidTypeForContainer[Double] {}
}

abstract class IsContainer[A, B: IsValidTypeForContainer] {
  def getElement(self: A, i: Int): B
  def get[R](self: A, ref: R)(implicit gets: GetsFromContainerMax[A, R, B]): gets.Out = gets.get(self, ref)
  def fromList(self: A, other: List[B]): A = ???
}
implicit class IsContainerOps[A, B: IsValidTypeForContainer](self: A)(implicit 
  isCont: IsContainer[A, B],
) {
  def getElement(i: Int) = isCont.getElement(self, i)
  def get[R](ref: R)(implicit gets: GetsFromContainerMax[A, R, B]): gets.Out = isCont.get(self, ref)
  def fromList(other: List[B]): A = isCont.fromList(self, other)
}

abstract class GetsFromContainerMax[A, R, B: IsValidTypeForContainer] {
  type Out
  def get(self: A, ref: R): Out
}
object GetsFromContainerMax {
  type Aux[A, R, B, O] = GetsFromContainerMax[A, R, B] { type Out = O }
  def instance[A, R, B: IsValidTypeForContainer, O](func: (A, R) => O): Aux[A, R, B, O] = new GetsFromContainerMax[A, R, B] {
    type Out = O
    def get(self: A, ref: R): Out = func(self, ref)
  }
  implicit def getsForListInt[A, B: IsValidTypeForContainer](implicit 
    isCons: IsContainer[A, B],
  ): Aux[A, List[Int], B, A] = instance(
    (self: A, ref: List[Int]) => {
      val lst = ref.map(isCons.getElement(self, _)).toList
      isCons.fromList(self, lst)
    }
  )
}

在给我 GetsContainerMax 类型类的地方,三个参数-一个用于 IsContainer 对象,一个用于引用,一个用于 IsContainer 对象.

Where I have given the GetsContainerMax typeclass three parameters - one for the IsContainer object, one for the reference and one for the data type of the IsContainer object.

当我尝试使用它时,出现编译错误:

When I then try to use this, I get a compile error:

case class List1[B: IsValidTypeForContainer] (
  data: List[B]
)
implicit def list1IsContainer[B: IsValidTypeForContainer] = new IsContainer[List1[B], B] {
  def getElement(self: List1[B], i: Int): B = self.data(i)
  def fromList(self: List1[B], other: List[B]): List1[B] = ???
}

val l1 = List1[Int](List(1,2,3))
implicitly[IsContainer[List1[Int], Int]].get(l1, List(1,2)) // Works
implicitly[List1[Int] => IsContainerOps[List1[Int], Int]] // Works
l1.get(List(1,2)) // Does not work

如果我使用-Xlog-implicits构建参数,它将告诉我

If I use the -Xlog-implicits build parameter, it tells me that

ambiguous implicit values: both value IntIsValid in object IsValidTypeForContainer of type example.Test.IsValidTypeForContainer[Int] and value DoubleIsValid in object IsValidTypeForContainer of type example.Test.IsValidTypeForContainer[Double] match expected type example.Test.IsValidTypeForContainer[B]

似乎有道理;大概我通过使用通用的 B 对类型类进行参数化,将这两个隐式都纳入了范围.

Which seems to make sense; presumably I am bringing both of these implicits into scope by parameterizing the typeclass with the generic B.

因此,我的下一个想法是尝试将 IsValidTypeForContainer 的通用参数的数量减少到最少,以便每个 R 类型的作用域中只有一个typeclass,就像这样:

My next thought was therefore to try to reduce the number of generic parameters for IsValidTypeForContainer to the minimum, in order to have only one typeclass in scope per type of R, likeso:

abstract class GetsFromContainerMin[R] {
  type Out
  def get[A](self: A, ref: R)(implicit isCont: IsContainer[A, _]): Out
}
object GetsFromContainerMin {
  type Aux[R, O] = GetsFromContainerMin[R] { type Out = O }
  def instance[A, R, O](func: (A, R) => O): Aux[R, O] = new GetsFromContainerMin[R] {
    type Out = O
    def get(self: A, ref: R)(implicit isCont: IsContainer[A, _]): Out = func(self, ref)
  }
  implicit def getsForListInt[A](implicit isCons: IsContainer[A, _]): Aux[List[Int], A] = instance(
    (self: A, ref: List[Int]) => {
      val lst = ref.map(isCons.getElement(self, _)).toList
      isCons.fromList(self, lst) // type mismatch - found: List[Any], required: List[_$3]
    }
  )
}

但这似乎不仅解决了问题,而且产生了一个额外的错误,因为编译器无法再进行类型检查,即类型B是否实现了 IsValidTypeForContainer .

But this seems to not only not solve the problem, but to generate an additional error in that the compiler can no longer type-check that type B implements IsValidTypeForContainer.

非常感谢收到任何帮助.

Any help gratefully received.

推荐答案

关于 GetsFromContainerMin ,只有没有多态方法的类型类才能具有构造函数方法 instance (在伴随对象中)由于Scala 2中缺少多态功能,因此在Scala 3中您可以编写

Regarding GetsFromContainerMin, only type classes without polymorphic methods can have constructor method instance (in companion object) because of the lack of polymorphic functions in Scala 2. In Scala 3 you'll be able to write

def instance[R, O](func: [A] => (A, R) => O): Aux[R, O] = ...

到目前为止,您必须写

object GetsFromContainerMin {
    type Aux[R, O] = GetsFromContainerMin[R] { type Out = O }

    implicit def getsForListInt[A](implicit isCons: IsContainer[A, _]): Aux[List[Int], A] = new GetsFromContainerMin[List[Int]] {
      override type Out = A
      override def get[A1](self: A1, ref: List[Int])(implicit isCont: IsContainer[A1, _]): Out = {
        val lst = ref.map(isCons.getElement(self, _)).toList
//                                          ^^^^
        isCons.fromList(self, lst)
//                      ^^^^
      }
    }
  }

我猜编译错误很清楚

type mismatch;
 found   : self.type (with underlying type A1)
 required: A

关于第一个问题,

implicit class IsContainerOps[A, B: IsValidTypeForContainer](self: A)(implicit
  isCont: IsContainer[A, B]   
)

意味深长的

implicit class IsContainerOps[A, B](self: A)(implicit
  ev: IsValidTypeForContainer[B],
  isCont: IsContainer[A, B]
)

但是隐式的顺序很重要.如果将 IsContainerOps 修改为

but order of implicits is significant. If you modify IsContainerOps to

implicit class IsContainerOps[A, B](self: A)(implicit
  isCont: IsContainer[A, B],
  ev: IsValidTypeForContainer[B],
)

然后 l1.get(List(1,2))将会编译.

隐式性从左到右解决.您要首先(从 self 推断) A ,然后解析 IsContainer [A,B] ,因此推断出 B ,而解析出 IsValidTypeForContainer [B] ,反之亦然首先是将 A 解析为推断,然后解析 IsValidTypeForContainer [B] ,并且在此步骤中, B 不受限制, A B ,因此可能无法推断 B 或推断为 Nothing ,所以不是 IsContainer [A,B] 解决.我做了一些简化(并非每次交换隐式都会破坏解析),但是隐式解析和类型推断的一般策略如我所述.

Implicits are resolved from left to right. You want firstly A to be inferred (from self), then IsContainer[A, B] to be resolved, so B to be inferred and IsValidTypeForContainer[B] to be resolved and not vice versa firstly A to be inferred, then IsValidTypeForContainer[B] to be resolved, and at this step B is not restricted, there is no connection between A and B, so possibly B can be not inferred or inferred to be Nothing, so IsContainer[A, B] is not resolved. I'm a little simplifying (not every time when you swap implicits you break resolution) but general strategy of implicit resolution and type inference is as I described.

这篇关于处理隐式类型类冲突的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆