继Witness that an abstract type implements a typeclass之后 我试图在下面的代码片段中并行比较这两种方法:
// We want both ParamaterizedTC and WithAbstractTC (below) to check that
// their B parameter implements AddQuotes
abstract class AddQuotes[A] {
def inQuotes(self: A): String = s"${self.toString}"
}
implicit val intAddQuotes = new AddQuotes[Int] {}
abstract class ParamaterizedTC[A, _B](implicit ev: AddQuotes[_B]) {
type B = _B
def getB(self: A): B
def add1ToB(self: A): String = ev.inQuotes(getB(self)) // TC witness does not need to be at method level
}
abstract class WithAbstractTC[A] private {
// at this point the compiler has not established that type B implements AddQuotes, even if we have created
// this instance via the apply[A, _B] constructor below...
type B
def getB(self: A): B
def add1ToB(self: A)(implicit ev: AddQuotes[B]): String =
ev.inQuotes(getB(self)) // ... so here the typeclass witness has to occur on the method level
}
object WithAbstractTC {
// This constructor checks that B implements AddQuotes
def apply[A, _B: AddQuotes](getB: A => _B): WithAbstractTC[A] = new WithAbstractTC[A] {
type B = _B
def getB(self: A): B = getB(self)
}
// But we could also have a constructor that does not check, so the compiler can never be certain that
// for a given instance of WithAbstractTC, type B implements AddQuotes
def otherConstructor[A, _B](getB: A => _B): WithAbstractTC[A] { type B = _B } = new WithAbstractTC[A] {
type B = _B
def getB(self: A): B = getB(self)
}
}
case class Container[B: AddQuotes]( get: B )
// These are both fine
implicit def containerIsParamaterized[B: AddQuotes]: ParamaterizedTC[Container[B], B] =
new ParamaterizedTC[Container[B], B] { def getB(self: Container[B]): B = self.get }
implicit def containerIsWithAbstract[_B: AddQuotes]: WithAbstractTC[Container[_B]] =
WithAbstractTC[Container[_B], _B](self => self.get)
val contIsParamaterized: ParamaterizedTC[Container[Int], Int] =
implicitly[ParamaterizedTC[Container[Int], Int]]
val contIsWithAbstract: WithAbstractTC[Container[Int]] =
implicitly[WithAbstractTC[Container[Int]]]
implicitly[AddQuotes[contIsParamaterized.B]]
implicitly[AddQuotes[contIsWithAbstract.B]] // This is not fine
我的结论(如果我错了,请纠正我)是,如果类型类见证人存在于公共构造函数中(如下面的ParamaterizedTC
所示),则编译器始终可以确定B
实现了AddQuotes
。而如果将这个见证人放在类型类同伴对象的构造函数中(例如WithAbstractTC
),那么它就不能。与基于抽象类型的方法相比,这在某种程度上改变了基于类型参数的方法的使用。
答案 0 :(得分:2)
差别在于:在ParametrizedTC
中,该类的作用域是隐式的,而在WithAbstractTC
中则没有。但是当您有抽象类型时,没有什么可以阻止您添加它:
abstract class WithAbstractTC2[A] private {
type B
implicit val ev: AddQuotes[B]
def getB(self: A): B
def add1ToB(self: A): String =
ev.inQuotes(getB(self))
}
def apply[A, _B](getB: A => _B)(implicit _ev: AddQuotes[_B]): WithAbstractTC2[A] = new WithAbstractTC2[A] {
type B = _B
implicit val ev: AddQuotes[B] = _ev
def getB(self: A): B = getB(self)
}
不幸的是,类似的东西
def apply[A, _B: AddQuotes](getB: A => _B): WithAbstractTC2[A] = new WithAbstractTC2[A] {
type B = _B
implicit val ev: AddQuotes[B] = implicitly[AddQuotes[_B]]
def getB(self: A): B = getB(self)
}
因为它将在最接近的范围中选择隐式:它正在尝试定义的隐式。
答案 1 :(得分:1)
implicitly[AddQuotes[contIsWithAbstract.B]]
拒绝编译与单个/多个构造函数/ apply
方法或类型参数/类型成员差异无关。你只是失去类型的改进随处可见。编译器无法检查您是否丢失了类型优化。您有权放弃类型的优化而放弃类型。
如果您恢复类型细化,则代码会编译
object WithAbstractTC {
def apply[A, _B: AddQuotes](getB: A => _B): WithAbstractTC[A] {type B = _B} =
// ^^^^^^^^^^^^^
new WithAbstractTC[A] {
type B = _B
def getB(self: A): B = getB(self)
}
...
}
implicit def containerIsWithAbstract[_B: AddQuotes]:
WithAbstractTC[Container[_B]] { type B = _B } =
// ^^^^^^^^^^^^^^^
WithAbstractTC[Container[_B], _B](self => self.get)
val contIsWithAbstract: WithAbstractTC[Container[Int]] { type B = Int } =
// ^^^^^^^^^^^^^^^^
shapeless.the[WithAbstractTC[Container[Int]]]
//^^^^^^^^^^^^^
implicitly[AddQuotes[contIsWithAbstract.B]] // compiles
请注意,implicitly
失去了类型限制,shapeless.the
是安全版本。
当implicitly
不够具体时 https://typelevel.org/blog/2014/01/18/implicitly_existential.html
如何通过抽象隐式对类成员类型类使用类级别的隐式约束,请参见 @AlexeyRomanov 的答案。