处理隐式类型类冲突 [英] Dealing with implicit typeclass conflict
问题描述
我正在尝试解决一个模棱两可的隐式问题,并(相关地)弄清楚应采用哪种最佳实践来对类型类进行参数化.我遇到一种情况,我正在使用类型类来实现多态方法.我最初尝试了以下方法:
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屋!