假设我打算在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$
见证人隐式转换永远不会发生。我的问题是:
implicit proof: T => Witness.Lt[Int]
不是以下无形宏的成功召唤者? implicit def apply[T](t: T): Witness.Lt[T] = macro SingletonTypeMacros.convertImpl
我如何使用类型类和其他Scala功能来实现这种平滑的类型级推导回退?最好:
不使用宏
如果不可能,请不要使用白盒宏
如果也不是没有可能,请不要使用将被Dotty丢弃的宏
Witness.Aux[T]
implicit def apply[T]: Witness.Aux[T] = macro SingletonTypeMacros.materializeImpl[T]
和implicit conversion从类型T
到Witness.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
的隐式实例(像其他类型一样)取决于布尔值flagisView
。执行时
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
isView
是true
。但是当您这样做时
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]) ... ______________________________________
[isView
是false
。在简单情况下,隐式
A => B
的存在和从A
到B
的隐式转换是相同的
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
赋予Some
和v2
赋予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
给出Some
和v2
给出None
。如果您将
fromLit
设为黑匣子,它仍然可以工作,但将返回MayHaveWitness
而不是MayHaveWitness.Some
和MayHaveWitness.None
。