Considering the following example, derived from the official manual of Scala 3 on Context Abstractions:
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")
}
Theoretically, the view bound unbox should be used repeatedly until the function concat is found, in each iteration unbox is a valid candidate in scope. In practice, the compiler will report the following error (tested in 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])
What's the cause of this behaviour and how to make it work?
UPDATE 1: just tried the typeclass idea and Nope, still doesn't work:
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]
)
You could use a type class + extension method rather than implicit conversions
It can be dangerous to formulate your logic in terms of implicit conversions. They are tricky. They are resolved/typechecked differently than implicits of non-functional types.
The problem with inductive definitions
is to decide what to prefer: to define the implicit for arbitrary
A,B(i.e. to postpone type inference, lazy strategy) or to infer e.g.Bbased onA(i.e. to make type inference now, eager strategy). For implicit conversions (isView == true) the eager strategy is preferred, for all other non-functional types (isView == false) the lazy one is preferredhttps://github.com/scala/scala/blob/232521f418c1e2c535d3630b0a5b3972a06bbd4e/src/compiler/scala/tools/nsc/typechecker/Implicits.scala#L846-L866
Suppose a typeclass has any instances
then an instance is found:
implicitly[TC[Int, String]], but implicit conversionwill not work:
val s: String = 1 // errorScala: `ambigious implicit values` but the right value is not event found
What are the hidden rules regarding the type inference in resolution of implicit conversions?
In scala, are there any condition where implicit view won't be able to propagate to other implicit function?
When calling a scala function with compile-time macro, how to failover smoothly when it causes compilation errors?
Scala Kleisli throws an error in IntelliJ
https://contributors.scala-lang.org/t/can-we-wean-scala-off-implicit-conversions/4388
https://contributors.scala-lang.org/t/proposed-changes-and-restrictions-for-implicit-conversions/4923
How can I chain implicits in Scala?