在 Scala 中,是否存在隐式视图无法传播到其他隐式函数的情况?
In scala, are there any condition where implicit view won't be able to propagate to other implicit function?
假设定义了一个名为 'summoner' 的 class,它能够从作用域中调用隐式视图:
case class Summoner[R]() {
def summon[T](v: T)(implicit ev: T => R): R = ev(v)
}
我发现它大部分时间都有效,但也有一些情况不起作用,例如以下是使用 singleton-ops
库的(不太)简短案例:
import shapeless.Witness
import singleton.ops.+
import singleton.ops.impl.Op
trait Operand {
def +[
X >: this.type <: Operand,
Y <: Operand
](that: Y): Op2[X, Y] = {
Op2[X, Y](this, that)
}
}
object Operand {
abstract class ProvenToBe[O <: Arity]()(implicit val out: O) extends Operand {}
object ProvenToBe {
implicit class Trivial[O <: Arity, T <: ProvenToBe[O]](
val self: T
) extends Proof {
override type Out = O
override def out: Out = self.out
}
}
}
trait Proof extends Serializable {
def self: Operand
type Out <: Arity
def out: Out
}
object Proof {
trait Out_=[+O <: Arity] extends Proof {
type Out <: O
}
trait Invar[S] extends Out_=[Arity.Const[S]] {
type SS = S
}
}
trait Arity extends Operand {}
object Arity {
trait Const[S] extends Arity {
type SS = S
}
object Const {
implicit class Same[S](val self: Const[S]) extends Proof.Invar[S] {
override type Out = Const[S]
override def out: Const[S] = self
}
}
class FromOp[S <: Op]() extends Const[S]
object FromOp {
implicit def summon[S <: Op](implicit s: S): FromOp[S] = new FromOp[S]()
}
class FromLiteral[S <: Int](val w: Witness.Lt[Int]) extends Const[S] {}
object FromLiteral {
implicit def summon[S <: Int](implicit w: Witness.Aux[S]): FromLiteral[S] =
new FromLiteral[S](w)
}
def apply(w: Witness.Lt[Int]): FromLiteral[w.T] = {
FromLiteral.summon[w.T](w) //TODO: IDEA inspection error
}
}
case class Op2[
+A1 <: Operand,
+A2 <: Operand
](
a1: A1,
a2: A2
) extends Operand {}
object Op2 {
implicit class ProveInvar[
A1 <: Operand,
A2 <: Operand,
S1,
S2
](
val self: Op2[A1, A2]
)(
implicit
bound1: A1 => Proof.Invar[S1],
bound2: A2 => Proof.Invar[S2]
) extends Proof.Invar[S1 + S2] {
override type Out = Arity.FromOp[S1 + S2]
override def out: Out = new Arity.FromOp[S1 + S2]()
}
}
当尝试按原样使用隐式视图时:
implicit val a = Arity(3)
implicit val b = Arity(4)
val op = a + b
op: Proof // implicit view works
但是使用召唤师时:
val summoner = Summoner[Proof]()
summoner.summon(op) // oops
[Error] /home/peng/git/shapesafe/spike/src/main/scala/edu/umontreal/kotlingrad/spike/arity/package.scala:141: No implicit view available from edu.umontreal.kotlingrad.spike.arity.package.Op2[edu.umontreal.kotlingrad.spike.arity.package.Arity.FromLiteral[Int(3)],edu.umontreal.kotlingrad.spike.arity.package.Arity.FromLiteral[Int(4)]] => edu.umontreal.kotlingrad.spike.arity.package.Proof.
one error found
FAILURE: Build failed with an exception.
这个错误消息看起来很平淡,几乎类似于常见的隐式类型不匹配错误,但以前的用法已经排除了这种可能性。所以我的问题是:
这种行为的原因是什么?
你怎么知道的?
我告诉过你关于使用 reify
、-Xlog-implicits
调试隐式以及手动解析 In scala 2 or 3, is it possible to debug implicit resolution process in runtime?
中的隐式
如果你打印树
import scala.reflect.runtime.universe._
println(reify{
op: Proof
}.tree)
你会看到隐式转换是如何解决的
(App.this.Op2.ProveInvar(App.this.op)(((self) => Arity.this.Const.Same(self)), ((self) => Arity.this.Const.Same(self))): App.this.Proof)
确实,手动解决了
summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op)(t =>
Op2.ProveInvar(t)(a1 => Arity.Const.Same(a1), a2 => Arity.Const.Same(a2))
)
编译但编译器本身找不到隐式转换
summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op) //doesn't compile
如果您打开 -Xlog-implicits
,您将看到详细信息
Information: $conforms is not a valid implicit value for App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing] because:
hasMatchingSymbol reported error: type mismatch;
found : App.Arity.FromLiteral[3] => App.Arity.FromLiteral[3]
required: App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing]
summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op)
Information: Arity.this.Const.Same is not a valid implicit value for App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing] because:
hasMatchingSymbol reported error: type mismatch;
found : App.Arity.Const[Nothing] => App.Arity.Const.Same[Nothing]
required: App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing]
summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op)
Information: App.this.Op2.ProveInvar is not a valid implicit value for App.Op2[App.Arity.FromLiteral[3],App.Arity.FromLiteral[4]] => App.Proof because:
hasMatchingSymbol reported error: No implicit view available from App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing].
summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op)
正如我在 When calling a scala function with compile-time macro, how to failover smoothly when it causes compilation errors? 中告诉您的那样,您不能总是使用隐式参数 (implicit ev: T => R)
检查是否存在隐式转换。有时隐式实例 T => R
的存在与隐式转换 T => R
的存在不同(并非所有隐式转换都是基于类型 class 的)。尝试替换
val summoner = Summoner[Proof]()
summoner.summon(op) //doesn't compile
与
summonImplicitView[Proof](op) //compiles
def summonImplicitView[B] = new PartiallyAppliedSummonImplicitView[B]
class PartiallyAppliedSummonImplicitView[B] {
def apply[A](a: A): B = macro summonImplicitViewImpl[A, B]
}
def summonImplicitViewImpl[A: c.WeakTypeTag, B: c.WeakTypeTag](c: whitebox.Context)(a: c.Tree): c.Tree = {
import c.universe._
val tpA = weakTypeOf[A]
val tpB = weakTypeOf[B]
val view = c.inferImplicitView(tree = a, from = tpA, to = tpB, silent = false)
q"$view($a)"
}
您也可以尝试从 question
输入 class ImplicitView
case class Summoner[R]() {
def summon[T](v: T)(implicit ev: ImplicitView[T, R]): R = ev.instance(v)
}
val summoner = Summoner[Proof]()
summoner.summon(op) // compiles
但是这种类型 class 并不总是有效,因为它是基于类型的并且并非所有隐式转换都是基于类型的,它在隐式解析期间忽略 v
的值。
我想我终于找到了问题所在(如果我们修复它 Summoner
将在没有宏的情况下工作)。你又失去了类型优化。
case class Summoner[R]() {
def summon[T](v: T)(implicit ev: T => R): R = ev(v)
}
val summoner = Summoner[Proof {type Out <: Arity.FromOp[3 + 4]}]()
// or even
//val summoner = Summoner[Proof {type Out <: Arity.FromOp[3 + 4]; type SS = 3 + 4}]()
summoner.summon(op) //compiles
这就是 -Xlog-implicits
日志中有 Nothing
的原因。
我想我修复了你的代码。在编写逻辑时,您将隐式实例与隐式转换混合在一起。隐式转换很棘手。我建议只根据类型 classes (MyTransform
) 编写逻辑,然后如果需要转换,则根据这些类型 class 定义它们 (myConversion
) es.
// doesn't extend T => R intentionally
trait MyTransform[-T, +R] {
def transform(v: T): R
}
implicit def myConversion[T, R](v: T)(implicit mt: MyTransform[T, R]): R = mt.transform(v)
case class Summoner[R]() {
def summon[T](v: T)(implicit ev: MyTransform[T, R]): R = ev.transform(v)
}
trait Operand {
def +[
X >: this.type <: Operand,
Y <: Operand
](that: Y): Op2[X, Y] = {
Op2[X, Y](this, that)
}
}
object Operand {
abstract class ProvenToBe[O <: Arity]()(implicit val out: O) extends Operand {}
object ProvenToBe {
implicit def trivial[O <: Arity, T <: ProvenToBe[O]]: MyTransform[T, Trivial[O, T]] = self => new Trivial(self)
/*implicit*/ class Trivial[O <: Arity, T <: ProvenToBe[O]](
val self: T
) extends Proof {
override type Out = O
override def out: Out = self.out
}
}
}
trait Proof extends Serializable {
def self: Operand
type Out <: Arity
def out: Out
}
object Proof {
trait Out_=[+O <: Arity] extends Proof {
type Out <: O
}
trait Invar[S] extends Out_=[Arity.Const[S]] {
type SS = S
}
}
trait Arity extends Operand {}
object Arity {
trait Const[S] extends Arity {
type SS = S
}
object Const {
implicit def same[S]: MyTransform[Const[S], Same[S]] = self => new Same(self)
/*implicit*/ class Same[S](val self: Const[S]) extends Proof.Invar[S] {
override type Out = Const[S]
override def out: Const[S] = self
}
}
class FromOp[S <: Op]() extends Const[S]
object FromOp {
implicit def summon[S <: Op](implicit s: S): FromOp[S] = new FromOp[S]()
}
class FromLiteral[S <: Int](val w: Witness.Lt[Int]) extends Const[S] {}
object FromLiteral {
implicit def summon[S <: Int](implicit w: Witness.Aux[S]): FromLiteral[S] =
new FromLiteral[S](w)
}
def apply(w: Witness.Lt[Int]): FromLiteral[w.T] = {
FromLiteral.summon[w.T](w) //TODO: IDEA inspection error
}
}
case class Op2[
+A1 <: Operand,
+A2 <: Operand
](
a1: A1,
a2: A2
) extends Operand {}
object Op2 {
implicit def proveInvar[A1 <: Operand, A2 <: Operand, S1, S2](implicit
bound1: MyTransform[A1, Proof.Invar[S1]],
bound2: MyTransform[A2, Proof.Invar[S2]]
): MyTransform[Op2[A1, A2], ProveInvar[A1, A2, S1, S2]]
= self => new ProveInvar(self)
/*implicit*/ class ProveInvar[
A1 <: Operand,
A2 <: Operand,
S1,
S2
](
val self: Op2[A1, A2]
)/*(
implicit
bound1: A1 => Proof.Invar[S1],
bound2: A2 => Proof.Invar[S2]
)*/ extends Proof.Invar[S1 + S2] {
override type Out = Arity.FromOp[S1 + S2]
override def out: Out = new Arity.FromOp[S1 + S2]()
}
}
implicit val a = Arity(3)
implicit val b = Arity(4)
val op = a + b
op: Proof // compiles
val summoner = Summoner[Proof]()
summoner.summon(op) // compiles
假设定义了一个名为 'summoner' 的 class,它能够从作用域中调用隐式视图:
case class Summoner[R]() {
def summon[T](v: T)(implicit ev: T => R): R = ev(v)
}
我发现它大部分时间都有效,但也有一些情况不起作用,例如以下是使用 singleton-ops
库的(不太)简短案例:
import shapeless.Witness
import singleton.ops.+
import singleton.ops.impl.Op
trait Operand {
def +[
X >: this.type <: Operand,
Y <: Operand
](that: Y): Op2[X, Y] = {
Op2[X, Y](this, that)
}
}
object Operand {
abstract class ProvenToBe[O <: Arity]()(implicit val out: O) extends Operand {}
object ProvenToBe {
implicit class Trivial[O <: Arity, T <: ProvenToBe[O]](
val self: T
) extends Proof {
override type Out = O
override def out: Out = self.out
}
}
}
trait Proof extends Serializable {
def self: Operand
type Out <: Arity
def out: Out
}
object Proof {
trait Out_=[+O <: Arity] extends Proof {
type Out <: O
}
trait Invar[S] extends Out_=[Arity.Const[S]] {
type SS = S
}
}
trait Arity extends Operand {}
object Arity {
trait Const[S] extends Arity {
type SS = S
}
object Const {
implicit class Same[S](val self: Const[S]) extends Proof.Invar[S] {
override type Out = Const[S]
override def out: Const[S] = self
}
}
class FromOp[S <: Op]() extends Const[S]
object FromOp {
implicit def summon[S <: Op](implicit s: S): FromOp[S] = new FromOp[S]()
}
class FromLiteral[S <: Int](val w: Witness.Lt[Int]) extends Const[S] {}
object FromLiteral {
implicit def summon[S <: Int](implicit w: Witness.Aux[S]): FromLiteral[S] =
new FromLiteral[S](w)
}
def apply(w: Witness.Lt[Int]): FromLiteral[w.T] = {
FromLiteral.summon[w.T](w) //TODO: IDEA inspection error
}
}
case class Op2[
+A1 <: Operand,
+A2 <: Operand
](
a1: A1,
a2: A2
) extends Operand {}
object Op2 {
implicit class ProveInvar[
A1 <: Operand,
A2 <: Operand,
S1,
S2
](
val self: Op2[A1, A2]
)(
implicit
bound1: A1 => Proof.Invar[S1],
bound2: A2 => Proof.Invar[S2]
) extends Proof.Invar[S1 + S2] {
override type Out = Arity.FromOp[S1 + S2]
override def out: Out = new Arity.FromOp[S1 + S2]()
}
}
当尝试按原样使用隐式视图时:
implicit val a = Arity(3)
implicit val b = Arity(4)
val op = a + b
op: Proof // implicit view works
但是使用召唤师时:
val summoner = Summoner[Proof]()
summoner.summon(op) // oops
[Error] /home/peng/git/shapesafe/spike/src/main/scala/edu/umontreal/kotlingrad/spike/arity/package.scala:141: No implicit view available from edu.umontreal.kotlingrad.spike.arity.package.Op2[edu.umontreal.kotlingrad.spike.arity.package.Arity.FromLiteral[Int(3)],edu.umontreal.kotlingrad.spike.arity.package.Arity.FromLiteral[Int(4)]] => edu.umontreal.kotlingrad.spike.arity.package.Proof.
one error found
FAILURE: Build failed with an exception.
这个错误消息看起来很平淡,几乎类似于常见的隐式类型不匹配错误,但以前的用法已经排除了这种可能性。所以我的问题是:
这种行为的原因是什么?
你怎么知道的?
我告诉过你关于使用 reify
、-Xlog-implicits
调试隐式以及手动解析 In scala 2 or 3, is it possible to debug implicit resolution process in runtime?
如果你打印树
import scala.reflect.runtime.universe._
println(reify{
op: Proof
}.tree)
你会看到隐式转换是如何解决的
(App.this.Op2.ProveInvar(App.this.op)(((self) => Arity.this.Const.Same(self)), ((self) => Arity.this.Const.Same(self))): App.this.Proof)
确实,手动解决了
summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op)(t =>
Op2.ProveInvar(t)(a1 => Arity.Const.Same(a1), a2 => Arity.Const.Same(a2))
)
编译但编译器本身找不到隐式转换
summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op) //doesn't compile
如果您打开 -Xlog-implicits
,您将看到详细信息
Information: $conforms is not a valid implicit value for App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing] because:
hasMatchingSymbol reported error: type mismatch;
found : App.Arity.FromLiteral[3] => App.Arity.FromLiteral[3]
required: App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing]
summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op)
Information: Arity.this.Const.Same is not a valid implicit value for App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing] because:
hasMatchingSymbol reported error: type mismatch;
found : App.Arity.Const[Nothing] => App.Arity.Const.Same[Nothing]
required: App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing]
summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op)
Information: App.this.Op2.ProveInvar is not a valid implicit value for App.Op2[App.Arity.FromLiteral[3],App.Arity.FromLiteral[4]] => App.Proof because:
hasMatchingSymbol reported error: No implicit view available from App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing].
summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op)
正如我在 When calling a scala function with compile-time macro, how to failover smoothly when it causes compilation errors? 中告诉您的那样,您不能总是使用隐式参数 (implicit ev: T => R)
检查是否存在隐式转换。有时隐式实例 T => R
的存在与隐式转换 T => R
的存在不同(并非所有隐式转换都是基于类型 class 的)。尝试替换
val summoner = Summoner[Proof]()
summoner.summon(op) //doesn't compile
与
summonImplicitView[Proof](op) //compiles
def summonImplicitView[B] = new PartiallyAppliedSummonImplicitView[B]
class PartiallyAppliedSummonImplicitView[B] {
def apply[A](a: A): B = macro summonImplicitViewImpl[A, B]
}
def summonImplicitViewImpl[A: c.WeakTypeTag, B: c.WeakTypeTag](c: whitebox.Context)(a: c.Tree): c.Tree = {
import c.universe._
val tpA = weakTypeOf[A]
val tpB = weakTypeOf[B]
val view = c.inferImplicitView(tree = a, from = tpA, to = tpB, silent = false)
q"$view($a)"
}
您也可以尝试从 question
输入 classImplicitView
case class Summoner[R]() {
def summon[T](v: T)(implicit ev: ImplicitView[T, R]): R = ev.instance(v)
}
val summoner = Summoner[Proof]()
summoner.summon(op) // compiles
但是这种类型 class 并不总是有效,因为它是基于类型的并且并非所有隐式转换都是基于类型的,它在隐式解析期间忽略 v
的值。
我想我终于找到了问题所在(如果我们修复它 Summoner
将在没有宏的情况下工作)。你又失去了类型优化。
case class Summoner[R]() {
def summon[T](v: T)(implicit ev: T => R): R = ev(v)
}
val summoner = Summoner[Proof {type Out <: Arity.FromOp[3 + 4]}]()
// or even
//val summoner = Summoner[Proof {type Out <: Arity.FromOp[3 + 4]; type SS = 3 + 4}]()
summoner.summon(op) //compiles
这就是 -Xlog-implicits
日志中有 Nothing
的原因。
我想我修复了你的代码。在编写逻辑时,您将隐式实例与隐式转换混合在一起。隐式转换很棘手。我建议只根据类型 classes (MyTransform
) 编写逻辑,然后如果需要转换,则根据这些类型 class 定义它们 (myConversion
) es.
// doesn't extend T => R intentionally
trait MyTransform[-T, +R] {
def transform(v: T): R
}
implicit def myConversion[T, R](v: T)(implicit mt: MyTransform[T, R]): R = mt.transform(v)
case class Summoner[R]() {
def summon[T](v: T)(implicit ev: MyTransform[T, R]): R = ev.transform(v)
}
trait Operand {
def +[
X >: this.type <: Operand,
Y <: Operand
](that: Y): Op2[X, Y] = {
Op2[X, Y](this, that)
}
}
object Operand {
abstract class ProvenToBe[O <: Arity]()(implicit val out: O) extends Operand {}
object ProvenToBe {
implicit def trivial[O <: Arity, T <: ProvenToBe[O]]: MyTransform[T, Trivial[O, T]] = self => new Trivial(self)
/*implicit*/ class Trivial[O <: Arity, T <: ProvenToBe[O]](
val self: T
) extends Proof {
override type Out = O
override def out: Out = self.out
}
}
}
trait Proof extends Serializable {
def self: Operand
type Out <: Arity
def out: Out
}
object Proof {
trait Out_=[+O <: Arity] extends Proof {
type Out <: O
}
trait Invar[S] extends Out_=[Arity.Const[S]] {
type SS = S
}
}
trait Arity extends Operand {}
object Arity {
trait Const[S] extends Arity {
type SS = S
}
object Const {
implicit def same[S]: MyTransform[Const[S], Same[S]] = self => new Same(self)
/*implicit*/ class Same[S](val self: Const[S]) extends Proof.Invar[S] {
override type Out = Const[S]
override def out: Const[S] = self
}
}
class FromOp[S <: Op]() extends Const[S]
object FromOp {
implicit def summon[S <: Op](implicit s: S): FromOp[S] = new FromOp[S]()
}
class FromLiteral[S <: Int](val w: Witness.Lt[Int]) extends Const[S] {}
object FromLiteral {
implicit def summon[S <: Int](implicit w: Witness.Aux[S]): FromLiteral[S] =
new FromLiteral[S](w)
}
def apply(w: Witness.Lt[Int]): FromLiteral[w.T] = {
FromLiteral.summon[w.T](w) //TODO: IDEA inspection error
}
}
case class Op2[
+A1 <: Operand,
+A2 <: Operand
](
a1: A1,
a2: A2
) extends Operand {}
object Op2 {
implicit def proveInvar[A1 <: Operand, A2 <: Operand, S1, S2](implicit
bound1: MyTransform[A1, Proof.Invar[S1]],
bound2: MyTransform[A2, Proof.Invar[S2]]
): MyTransform[Op2[A1, A2], ProveInvar[A1, A2, S1, S2]]
= self => new ProveInvar(self)
/*implicit*/ class ProveInvar[
A1 <: Operand,
A2 <: Operand,
S1,
S2
](
val self: Op2[A1, A2]
)/*(
implicit
bound1: A1 => Proof.Invar[S1],
bound2: A2 => Proof.Invar[S2]
)*/ extends Proof.Invar[S1 + S2] {
override type Out = Arity.FromOp[S1 + S2]
override def out: Out = new Arity.FromOp[S1 + S2]()
}
}
implicit val a = Arity(3)
implicit val b = Arity(4)
val op = a + b
op: Proof // compiles
val summoner = Summoner[Proof]()
summoner.summon(op) // compiles