在 Scala 2/3 中,为什么不能链接拆箱或视图边界(如在 OCaml 中),以及如何修复/规避它?

问题描述 投票:0回答:1

考虑以下示例,源自 Scala 3 上下文抽象官方手册:

https://docs.scala-lang.org/scala3/guides/migration/incompat-contextual-abstractions.html#view-bounds

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]
    )
scala implicit-conversion implicit scala-3 view-bound
1个回答
0
投票

您可以使用类型类+扩展方法而不是隐式转换

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
),首选惰性策略

https://github.com/scala/scala/blob/232521f418c1e2c535d3630b0a5b3972a06bbd4e/src/compiler/scala/tools/nsc/typechecker/Implicits.scala#L846-L866

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函数时,导致编译错误时如何顺利故障转移?

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

如何在 Scala 中链接隐式?

© www.soinside.com 2019 - 2024. All rights reserved.