当使用编译时宏调用scala函数时,如何在导致编译错误时顺利进行故障转移?

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

假设我打算在scala程序中使用单例/文字类型功能,此功能在scala 2.12的无形状库中提供(scala 2.13支持本机文字类型,但让我们以shapeless为例)

在无形状中,文字类型表示为见证对象的路径相关的内部类型,可以从scala文字/ const中隐式转换:


import com.tribbloids.spike.BaseSpec
import shapeless.Witness

import scala.util.Random

    val w: Witness.Lt[Int] = 3

    val w2: Witness.Lt[Int] = Random.nextInt(3) // this doesn't compile

第二行导致编译抛出异常:


[Error] .../WitnessSuite.scala:14: Expression scala.util.Random.nextInt(3) does not evaluate to a constant or a stable reference value
one error found

现在,假设我想编写类似Option[Witness.Lt[Int]]的东西,无论它是否为文字,都可以从Int进行转换。在scala类型的类约定中,我应该写这样的东西:

    trait MayHaveWitness {

      type Lit
    }

    trait MayHaveWitness_Implicits0 {

      class Some(val w: Witness.Lt[Int]) extends MayHaveWitness {

        type Lit = w.T
      }
      object None extends MayHaveWitness {

        type Lit = Nothing
      }

      implicit def fromNonLit(v: Int): None.type = None
    }

    object MayHaveWitness extends MayHaveWitness_Implicits0 {

      implicit def fromLit[T](literal: T)(implicit proof: T => Witness.Lt[Int]): MayHaveWitness.Some = new Some(literal)
    }

    val v1: MayHaveWitness = 3
    println(v1.getClass)

    val v2: MayHaveWitness = Random.nextInt(3)
    println(v2.getClass)

MayHaveWitness_Implicits0的级别较低,理论上,如果证人隐式转换成功,则应该被fromLit遮盖。不幸的是,当我执行这段代码时,我得到的只是:

class com.tribbloids.spike.shapeless_spike.WitnessSuite$MayHaveWitness_Implicits0$1$None$
class com.tribbloids.spike.shapeless_spike.WitnessSuite$MayHaveWitness_Implicits0$1$None$

见证人隐式转换永远不会发生。我的问题是:

  1. 为什么implicit proof: T => Witness.Lt[Int]不是以下无形宏的成功召唤者?
  implicit def apply[T](t: T): Witness.Lt[T] = macro SingletonTypeMacros.convertImpl
  1. 我如何使用类型类和其他Scala功能来实现这种平滑的类型级推导回退?最好:

    • 不使用宏

    • 如果不可能,请不要使用白盒宏

    • 如果也不是没有可能,请不要使用将被Dotty丢弃的宏

scala shapeless scala-macros dependent-type path-dependent-type
1个回答
0
投票
无形状定义implicit instance类型的Witness.Aux[T]

implicit def apply[T]: Witness.Aux[T] = macro SingletonTypeMacros.materializeImpl[T]

implicit conversion从类型TWitness.Lt[T]

implicit def apply[T](t: T): Witness.Lt[T] = macro SingletonTypeMacros.convertImpl

隐式实例Witness.Aux[T]仅根据类型T解析(或不基于T is a singleton type or nor),就像普通类型类的隐式实例一样。但是隐式转换T => Witness.Lt[T]与普通的隐式转换不同。普通隐式转换是根据要转换的值的类型解析还是不解析。但是T => Witness.Lt[T]的解析不仅取决于类型T,还取决于值t本身(t is constant/stable or not)。

如果打开scalacOptions ++= Seq("-Ymacro-debug-lite", "-Xlog-implicits"),您会在[]中看到它>

val w: Witness.Lt[Int] = 3 //compiles //Warning:scalac: performing macro expansion shapeless.this.Witness.apply[Int](3) at source-/media/data/Projects/macrosdemo213/core/src/main/scala/App114_2.scala,line-9,offset=205 //Warning:scalac: _root_.shapeless.Witness.mkWitness[Int(3)](3.asInstanceOf[Int(3)]) val w2: Witness.Lt[Int] = Random.nextInt(3) //doesn't compile //Warning:scalac: performing macro expansion shapeless.this.Witness.apply[Int](scala.util.Random.nextInt(3)) at source-/media/data/Projects/macrosdemo213/core/src/main/scala/App114_2.scala,line-10,offset=249 //Warning:scalac: macro expansion has failed: Expression scala.util.Random.nextInt(3) does not evaluate to a constant or a stable reference value //Error: Expression scala.util.Random.nextInt(3) does not evaluate to a constant or a stable reference value

仅选中implicit def apply[T](t: T): Witness.Lt[T](在w中可用,但在w2中不可用)。

也位于

val v1: MayHaveWitness = 3 // compiles but gives None //Warning:scalac: macro expansion is delayed: shapeless.this.Witness.apply[T] //Warning:scalac: performing macro expansion shapeless.this.Witness.apply[T] //Warning:scalac: macro expansion has failed: Type argument T is not a singleton type //Information: shapeless.this.Witness.apply is not a valid implicit value for Int => shapeless.Witness.Lt[Int] because: //hasMatchingSymbol reported error: polymorphic expression cannot be instantiated to expected type; // found : [T]shapeless.Witness.Aux[T] // (which expands to) [T]shapeless.Witness{type T = T} // required: Int => shapeless.Witness.Lt[Int] // (which expands to) Int => shapeless.Witness{type T <: Int} //Information: App.this.MayHaveWitness.fromLit is not a valid implicit value for Int(3) => App.MayHaveWitness because: //No implicit view available from Int => shapeless.Witness.Lt[Int].

和]中>

val v2: MayHaveWitness = Random.nextInt(3) // compiles but gives None //Warning:scalac: macro expansion is delayed: shapeless.this.Witness.apply[T] //Warning:scalac: performing macro expansion shapeless.this.Witness.apply[T] //Warning:scalac: macro expansion has failed: Type argument T is not a singleton type //Warning:scalac: performing macro expansion shapeless.this.Witness.apply[T] //Information: App.this.MayHaveWitness.fromLit is not a valid implicit value for Int => App.MayHaveWitness because: //No implicit view available from Int => shapeless.Witness.Lt[Int]. //Information: shapeless.this.Witness.apply is not a valid implicit value for Int => shapeless.Witness.Lt[Int] because: //hasMatchingSymbol reported error: polymorphic expression cannot be instantiated to expected type; // found : [T]shapeless.Witness.Aux[T] // (which expands to) [T]shapeless.Witness{type T = T} // required: Int => shapeless.Witness.Lt[Int] // (which expands to) Int => shapeless.Witness{type T <: Int} //Information: App.this.MayHaveWitness.fromLit is not a valid implicit value for Int => App.MayHaveWitness because: //No implicit view available from Int => shapeless.Witness.Lt[Int].

implicit def apply[T]: Witness.Aux[T]implicit def apply[T](t: T): Witness.Lt[T]均已检查,但都不起作用。

为什么implicit proof: T => Witness.Lt[Int]不是以下无形宏的成功召唤者?

编译器对待功能类型A => B的隐式与其他类型的隐式的处理方式不同。可以将它们视为隐式转换(视图)。但是,是将它们实际上视为转换还是仅将类型A => B的隐式实例(像其他类型一样)取决于布尔值flag isView

执行时

val w: Witness.Lt[Int] = 3 //compiles val w2: Witness.Lt[Int] = Random.nextInt(3) //doesn't compile val v1: MayHaveWitness = 3 //compiles val v2: MayHaveWitness = Random.nextInt(3) //compiles

isViewtrue。但是当您这样做时

implicitly[Int => Witness.Lt[Int]] //doesn't compile implicitly[3 => Witness.Lt[Int]] //doesn't compile implicitly[Int => MayHaveWitness] //doesn't compile implicitly[3 => MayHaveWitness] //doesn't compile

或这里

implicit def fromLit... (implicit proof: T => Witness.Lt[Int]) ... ______________________________________

[isViewfalse

在简单情况下,隐式A => B的存在和从AB的隐式转换是相同的

class A class B // implicit val aToB: A => B = null // this one implicit def aToB(a: A): B = null // or this one implicitly[A => B] //compiles val b: B = new A //compiles

但不是我们的情况。有隐式转换3 => Witness.Lt[3],但没有这种类型的实例

val w: Witness.Lt[3] = 3.asInstanceOf[3] //compiles implicitly[3 => Witness.Lt[3]] // doesn't compile //Information: shapeless.this.Witness.apply is not a valid implicit value for 3 => shapeless.Witness.Lt[3] because: //hasMatchingSymbol reported error: polymorphic expression cannot be instantiated to expected type; // found : [T]shapeless.Witness.Aux[T] // (which expands to) [T]shapeless.Witness{type T = T} // required: 3 => shapeless.Witness.Lt[3] // (which expands to) 3 => shapeless.Witness{type T <: 3} //Error: No implicit view available from 3 => shapeless.Witness.Lt[3].

因此它检查implicit def apply[T]: Witness.Aux[T],但不检查implicit def apply[T](t: T): Witness.Lt[T]。我没有深入调试隐式解析,但我怀疑在解析隐式之前无法推断出某种类型。

没有一种标准方法可以打开isView,以便在proof中解析... def fromLit... (implicit proof: T => Witness.Lt[Int]) ...时完全模拟隐式转换的行为。如果我们使用isView而不是c.inferImplicitView],则可以使用宏打开c.inferImplicitValue

import scala.language.experimental.macros import scala.reflect.macros.whitebox trait ImplicitView[A, B] { def instance: A => B } object ImplicitView { implicit def mkImplicitView[A, B]: ImplicitView[A, B] = macro mkImplicitViewImpl[A, B] def mkImplicitViewImpl[A: c.WeakTypeTag, B: c.WeakTypeTag](c: whitebox.Context): c.Tree = { import c.universe._ val tpA = weakTypeOf[A] val tpB = weakTypeOf[B] val x = TermName(c.freshName("x")) val instance = c.inferImplicitView(tree = EmptyTree, from = tpA, to = tpB, silent = false) q"""new ImplicitView[$tpA, $tpB] { def instance: $tpA => $tpB = ($x: $tpA) => $instance($x) }""" }

让我们替换

implicit def fromLit[T](literal: T)(implicit proof: T => Witness.Lt[Int]): MayHaveWitness.Some = new Some(literal)

implicit def fromLit[T](literal: T)(implicit proof: ImplicitView[T, Witness.Lt[Int]]): MayHaveWitness.Some = new Some(proof.instance(literal))

还必须修改

implicit def fromNonLit(v: Int): None.type = None

因为它与fromLit不明确。原因类似于those。最简单的解决方法是将其替换为

implicit def fromNonLit[T](v: T): None.type = None

现在都

val v1: MayHaveWitness = 3 println(v1.getClass) val v2: MayHaveWitness = Random.nextInt(3) println(v2.getClass)

Some(我怀疑那不是您想要的)。这是可以理解的。 Random.nextInt(3)Int。而且,我们仅基于类型来解析MayHaveWitness。并且存在隐式转换Int => Witness.Lt[Int]。所以是Some

所以看来,如果我们要v1赋予Somev2赋予None,那么我们就不能仅基于类型来做到这一点。因此,使用类型类的方法将不起作用,我们必须使用宏。

trait MayHaveWitness { type Lit } object MayHaveWitness { class Some(val w: Witness.Lt[Int]) extends MayHaveWitness { type Lit = w.T } object None extends MayHaveWitness { type Lit = Nothing } implicit def fromLit[T](literal: T): MayHaveWitness = macro fromLitImpl[T] def fromLitImpl[T: c.WeakTypeTag](c: whitebox.Context)(literal: c.Tree): c.Tree = { import c.universe._ val conversion = c.inferImplicitView(tree = literal, from = weakTypeOf[T], to = typeOf[Witness.Lt[Int]], silent = false) util.Try(c.typecheck(q"new MayHaveWitness.Some($conversion($literal))")) .getOrElse(q"MayHaveWitness.None") } }

在这里,我们将(implicit proof: T => Witness.Lt[Int])替换为c.inferImplicitView...,不仅探讨了literal的类型,而且探讨了literal本身。

现在]

val v1: MayHaveWitness = 3 println(v1.getClass) val v2: MayHaveWitness = Random.nextInt(3) println(v2.getClass)

[v1给出Somev2给出None

如果您将fromLit设为黑匣子,它仍然可以工作,但将返回MayHaveWitness而不是MayHaveWitness.SomeMayHaveWitness.None

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