考虑以下示例,源自 Scala 3 上下文抽象官方手册:
object ViewBound {
trait Boxed[T] {
def v: T
}
object Boxed {
implicit def unbox[T, R](
boxed: Boxed[T]
)(
implicit
more: T => R
): R = more(boxed.v)
}
case class B0(v: String) extends Boxed[String]
case class B1(v: B0) extends Boxed[B0]
case class B2(v: B1) extends Boxed[B1]
val b2 = B2(B1(B0("a")))
val ab = b2.concat("b")
}
理论上,视图边界
unbox
应该被重复使用,直到找到函数concat
,在每次迭代中unbox
都是范围内的有效候选者。实际中,编译器会报如下错误(在Scala 3.4.2-snapshot build中测试):
[Error] /home/peng/git/dottyspike/src/main/scala/com/tribbloids/spike/dotty/ViewBound.scala:25:15: value concat is not a member of com.tribbloids.spike.dotty.ViewBound.B2.
An extension method was tried, but could not be fully constructed:
com.tribbloids.spike.dotty.ViewBound.Boxed.unbox[
com.tribbloids.spike.dotty.ViewBound.B1,
com.tribbloids.spike.dotty.ViewBound.B1](
com.tribbloids.spike.dotty.ViewBound.b2)(
$conforms[com.tribbloids.spike.dotty.ViewBound.B1])
此行为的原因是什么以及如何使其发挥作用?
更新1:刚刚尝试了类型类的想法,不,仍然不起作用:
object V2 {
trait Boxed[T] {
def self: T
}
object Boxed {
trait ViewBound[S, R] extends (S => R) {}
implicit def unbox1[T]: ViewBound[Boxed[T], T] = v => v.self
implicit def unboxMore[T, R](
implicit
more: ViewBound[T, R]
): ViewBound[Boxed[T], R] = v => more(v.self)
implicit def convert[T, R](v: Boxed[T])(
implicit
unbox: ViewBound[Boxed[T], R]
): R = unbox(v)
}
case class B0(self: String) extends Boxed[String]
val b0 = B0("a")
b0.concat("b")
case class B1(self: B0) extends Boxed[B0]
val b1 = B1(B0("a"))
b1.concat("b")
case class B2(self: B1) extends Boxed[B1]
val b2 = B2(B1(B0("a")))
b2.concat("b")
}
[Error] /home/peng/git/dottyspike/src/main/scala/com/tribbloids/spike/dotty/ViewBound.scala:27:17: value concat is not a member of com.tribbloids.spike.dotty.ViewBound.Failed.B2.
An extension method was tried, but could not be fully constructed:
com.tribbloids.spike.dotty.ViewBound.Failed.Boxed.unbox[
com.tribbloids.spike.dotty.ViewBound.Failed.B1,
com.tribbloids.spike.dotty.ViewBound.Failed.B1](
com.tribbloids.spike.dotty.ViewBound.Failed.b2)(
$conforms[com.tribbloids.spike.dotty.ViewBound.Failed.B1])
[Error] /home/peng/git/dottyspike/src/main/scala/com/tribbloids/spike/dotty/ViewBound.scala:59:8: value concat is not a member of com.tribbloids.spike.dotty.ViewBound.Success.B1.
An extension method was tried, but could not be fully constructed:
com.tribbloids.spike.dotty.ViewBound.Success.Boxed.convert[
com.tribbloids.spike.dotty.ViewBound.Success.B0,
com.tribbloids.spike.dotty.ViewBound.Success.B0](
com.tribbloids.spike.dotty.ViewBound.Success.b1)(
com.tribbloids.spike.dotty.ViewBound.Success.Boxed.unbox1[
com.tribbloids.spike.dotty.ViewBound.Success.B0]
)
您可以使用类型类+扩展方法而不是隐式转换
trait Unbox[-A, +B] {
def apply(a: A): B
}
object Unbox {
implicit def recurse[A, B](implicit u: Unbox[A, B]): Unbox[Boxed[A], B] = boxed => u(boxed.v)
implicit def base[A]: Unbox[Boxed[A], A] = _.v
}
implicit class BoxedOps[T](val boxed: T) extends AnyVal {
def concat(s: String)(implicit u: Unbox[T, String]): String = u(boxed).concat(s)
}
case class B0(v: String) extends Boxed[String]
case class B1(v: B0) extends Boxed[B0]
case class B2(v: B1) extends Boxed[B1]
val b0 = B0("a")
val b1 = B1(b0)
val b2 = B2(b1)
b0.concat("b") //ab
b1.concat("b") //ab
b2.concat("b") //ab
用隐式转换来表述逻辑可能很危险。他们很棘手。它们的解析/类型检查与非函数类型的隐式不同。
归纳定义的问题
implicit def foo[A, B]...(implicit x: X[A, B])...
是决定更喜欢什么:定义任意
A
、B
的隐式(即推迟类型推断、惰性策略)或推断例如B
基于A
(即现在进行类型推断,急切策略)。对于隐式转换 (isView == true
),首选急切策略,对于所有其他非函数类型 (isView == false
),首选惰性策略
val itree2 = if (!isView) fallback else pt match {
case Function1(arg1, arg2) =>
typed1(
atPos(itree0.pos)(Apply(itree1, Ident(nme.argument).setType(approximate(arg1)) :: Nil)),
EXPRmode,
approximate(arg2)
) match {
// try to infer implicit parameters immediately in order to:
// 1) guide type inference for implicit views
// 2) discard ineligible views right away instead of risking spurious ambiguous implicits
//
// this is an improvement of the state of the art that brings consistency to implicit resolution rules
// (and also helps fundep materialization to be applicable to implicit views)
//
// there's one caveat though. we need to turn this behavior off for scaladoc
// because scaladoc usually doesn't know the entire story
// and is just interested in views that are potentially applicable
// for instance, if we have `class C[T]` and `implicit def conv[T: Numeric](c: C[T]) = ???`
// then Scaladoc will give us something of type `C[T]`, and it would like to know
// that `conv` is potentially available under such and such conditions
case tree if isImplicitMethodType(tree.tpe) && !isScaladoc =>
applyImplicitArgs(tree)
假设类型类有任何实例
trait TC[A, B]
implicit def mkTC[A, B]: TC[A, B] = null
然后找到一个实例:
implicitly[TC[Int, String]]
,但是隐式转换
implicit def conversion[A, B](a: A)(implicit tc: TC[A, B]): B = ???
不起作用:
val s: String = 1 // error
在scala中,是否存在隐式视图无法传播到其他隐式函数的情况?
使用编译时宏调用scala函数时,导致编译错误时如何顺利故障转移?
Scala Kleisli 在 IntelliJ 中抛出错误
https://contributors.scala-lang.org/t/can-we-wean-scala-off-implicit-conversions/4388
https://contributors.scala-lang.org/t/propose-changes-and-restrictions-for-implicit-conversions/4923