类型成员的上下文边界或如何将隐式解析推迟到成员实例化
Context bounds for type members or how to defer implicit resolution until member instantiation
在下面的示例中,是否有办法避免隐式解析选择 defaultInstance
并改用 intInstance
?代码后的更多背景:
// the following part is an external fixed API
trait TypeCls[A] {
def foo: String
}
object TypeCls {
def foo[A](implicit x: TypeCls[A]) = x.foo
implicit def defaultInstance[A]: TypeCls[A] = new TypeCls[A] {
def foo = "default"
}
implicit val intInstance: TypeCls[Int] = new TypeCls[Int] {
def foo = "integer"
}
}
trait FooM {
type A
def foo: String = implicitly[TypeCls[A]].foo
}
// end of external fixed API
class FooP[A:TypeCls] { // with type params, we can use context bound
def foo: String = implicitly[TypeCls[A]].foo
}
class MyFooP extends FooP[Int]
class MyFooM extends FooM { type A = Int }
object Main extends App {
println(s"With type parameter: ${(new MyFooP).foo}")
println(s"With type member: ${(new MyFooM).foo}")
}
实际输出:
With type parameter: integer
With type member: default
期望的输出:
With type parameter: integer
With type member: integer
我正在与第三方库合作,该库使用上述方案为类型 class TypeCls
提供 "default" 个实例。我认为上面的代码是演示我的问题的最小示例。
用户应该混合 FooM
特征并实例化抽象类型成员 A
。问题是由于 defaultInstance
(new MyFooM).foo
的调用没有解析专门的 intInstance
而是提交给 defaultInstance
这不是我想要的。
我添加了一个使用类型参数的替代版本,称为 FooP
(P = 参数,M = 成员),它避免通过使用类型参数上的上下文绑定来解析 defaultInstance
。
是否有对类型成员执行此操作的等效方法?
编辑:我的简化有一个错误,实际上 foo
不是 def
而是 val
,所以不可能添加隐式参数。所以目前的答案都不适用。
trait FooM {
type A
val foo: String = implicitly[TypeCls[A]].foo
}
// end of external fixed API
class FooP[A:TypeCls] { // with type params, we can use context bound
val foo: String = implicitly[TypeCls[A]].foo
}
在这种特定情况下,最简单的解决方案是 foo
本身需要 TypeCls[A]
的隐式实例。
唯一的缺点是它会在每次调用 foo
时传递,而不是仅在实例化时传递
FooM
。因此,您必须确保它们在每次调用 foo
时都在范围内。虽然只要 TypeCls
实例在伴随对象中,您就没有什么特别要做的。
trait FooM {
type A
def foo(implicit e: TypeCls[A]): String = e.foo
}
UPDATE:在我上面的回答中,我设法忽略了 FooM
无法修改的事实。此外,对问题的最新编辑提到 FooM.foo
实际上是 val
而不是 def
.
好吧,坏消息是您使用的 API 完全坏了。 FooM.foo
不可能 return 任何有用的东西(它总是将 TypeCls[A]
解析为 TypeCls.defaultInstance
而不管 A
的实际值)。唯一的出路是在 A
的实际值已知的派生 class 中覆盖 foo
,以便能够使用 TypeCls
的正确实例。幸运的是,这个想法可以与您使用带有上下文绑定的 class 的原始解决方法相结合(在您的情况下为 FooP
):
class FooMEx[T:TypeCls] extends FooM {
type A = T
override val foo: String = implicitly[TypeCls[A]].foo
}
现在不要让你的 class 直接扩展 FooM
,而是让它们扩展 FooMEx
:
class MyFoo extends FooMEx[Int]
FooMEx
和原来的 FooP
class 之间的唯一区别是 FooMEx
扩展了 FooM
, 所以 MyFoo
是 FooM
的一个正确实例,因此可以与固定的 API.
一起使用
能否从第三方库中复制代码。覆盖方法就可以了。
class MyFooM extends FooM { type A = Int
override def foo: String = implicitly[TypeCls[A]].foo}
这是一个 hack,但我怀疑还有什么更好的。
我不知道为什么会这样。它必须是某种顺序,其中类型别名在隐式表达式中被替换。
只有语言规范方面的专家才能告诉你确切的原因。
在下面的示例中,是否有办法避免隐式解析选择 defaultInstance
并改用 intInstance
?代码后的更多背景:
// the following part is an external fixed API
trait TypeCls[A] {
def foo: String
}
object TypeCls {
def foo[A](implicit x: TypeCls[A]) = x.foo
implicit def defaultInstance[A]: TypeCls[A] = new TypeCls[A] {
def foo = "default"
}
implicit val intInstance: TypeCls[Int] = new TypeCls[Int] {
def foo = "integer"
}
}
trait FooM {
type A
def foo: String = implicitly[TypeCls[A]].foo
}
// end of external fixed API
class FooP[A:TypeCls] { // with type params, we can use context bound
def foo: String = implicitly[TypeCls[A]].foo
}
class MyFooP extends FooP[Int]
class MyFooM extends FooM { type A = Int }
object Main extends App {
println(s"With type parameter: ${(new MyFooP).foo}")
println(s"With type member: ${(new MyFooM).foo}")
}
实际输出:
With type parameter: integer
With type member: default
期望的输出:
With type parameter: integer
With type member: integer
我正在与第三方库合作,该库使用上述方案为类型 class TypeCls
提供 "default" 个实例。我认为上面的代码是演示我的问题的最小示例。
用户应该混合 FooM
特征并实例化抽象类型成员 A
。问题是由于 defaultInstance
(new MyFooM).foo
的调用没有解析专门的 intInstance
而是提交给 defaultInstance
这不是我想要的。
我添加了一个使用类型参数的替代版本,称为 FooP
(P = 参数,M = 成员),它避免通过使用类型参数上的上下文绑定来解析 defaultInstance
。
是否有对类型成员执行此操作的等效方法?
编辑:我的简化有一个错误,实际上 foo
不是 def
而是 val
,所以不可能添加隐式参数。所以目前的答案都不适用。
trait FooM {
type A
val foo: String = implicitly[TypeCls[A]].foo
}
// end of external fixed API
class FooP[A:TypeCls] { // with type params, we can use context bound
val foo: String = implicitly[TypeCls[A]].foo
}
在这种特定情况下,最简单的解决方案是 foo
本身需要 TypeCls[A]
的隐式实例。
唯一的缺点是它会在每次调用 foo
时传递,而不是仅在实例化时传递
FooM
。因此,您必须确保它们在每次调用 foo
时都在范围内。虽然只要 TypeCls
实例在伴随对象中,您就没有什么特别要做的。
trait FooM {
type A
def foo(implicit e: TypeCls[A]): String = e.foo
}
UPDATE:在我上面的回答中,我设法忽略了 FooM
无法修改的事实。此外,对问题的最新编辑提到 FooM.foo
实际上是 val
而不是 def
.
好吧,坏消息是您使用的 API 完全坏了。 FooM.foo
不可能 return 任何有用的东西(它总是将 TypeCls[A]
解析为 TypeCls.defaultInstance
而不管 A
的实际值)。唯一的出路是在 A
的实际值已知的派生 class 中覆盖 foo
,以便能够使用 TypeCls
的正确实例。幸运的是,这个想法可以与您使用带有上下文绑定的 class 的原始解决方法相结合(在您的情况下为 FooP
):
class FooMEx[T:TypeCls] extends FooM {
type A = T
override val foo: String = implicitly[TypeCls[A]].foo
}
现在不要让你的 class 直接扩展 FooM
,而是让它们扩展 FooMEx
:
class MyFoo extends FooMEx[Int]
FooMEx
和原来的 FooP
class 之间的唯一区别是 FooMEx
扩展了 FooM
, 所以 MyFoo
是 FooM
的一个正确实例,因此可以与固定的 API.
能否从第三方库中复制代码。覆盖方法就可以了。
class MyFooM extends FooM { type A = Int
override def foo: String = implicitly[TypeCls[A]].foo}
这是一个 hack,但我怀疑还有什么更好的。
我不知道为什么会这样。它必须是某种顺序,其中类型别名在隐式表达式中被替换。
只有语言规范方面的专家才能告诉你确切的原因。