这是一个简单的例子:
object CoerciveCovariance {
trait Cov[+T]
def cast[A, B](v: Cov[A])(
implicit
ev: A <:< B
) = {
v: Cov[B]
}
}
它不编译:
CoerciveCovariance.scala:11:5: Found: (v : xxx.CoerciveCovariance.Cov[A])
Required: xxx.CoerciveCovariance.Cov[B]
one error found
是否很难让编译器找出从
Cov[A]
到Cov[B]
的缺失强制转换?为什么它不是默认行为?
从技术上讲,
A <:< B
是一种隐式转换,它不允许编译器访问以下知识:对于存在协方差的每个 A
都可以使用 B
,并且对于存在逆变的每个 B
A
可以使用。
而
<:<
的值由scala.<:<
同伴提供(召唤<:<
为implicit def
找到=:=
然后使用方差和子类型规则将其更改为A <:< B
),所以有点上标准库级别而不是语言级别。所以你可以例如禁用导入scala
并提供你自己的东西。
为了直接向类型系统通知类型关系,您应该使用类型边界而不是证据值和“广义类型边界”:
object CoerciveCovariance {
trait Cov[+T]
def cast[A, B >: A](v: Cov[A]) = {
v: Cov[B]
}
}
或者如果您真的对扩展
<:<
能力特别感兴趣,您可以提供自己的隐式转换:
object CoerciveCovariance {
trait Cov[+T]
import scala.language.implicitConversions
implicit def castF[F[+_], A, B](fa: F[A])(implicit ev: A <:< B): F[B] =
ev.substituteCo(fa)
def cast[A, B](v: Cov[A])(implicit
ev: A <:< B
): Cov[B] = {
v: Cov[B]
}
}
因为类型推断和隐式解析是不同的。
他们互相影响。事实上,类型推断会影响隐式解析
trait TC[A]
implicit val int: TC[Int] = null
def foo[A](a: A)(implicit tc: TC[A]) = null
foo(1) // compiles
foo("a") // doesn't compile
这里首先类型
A
被推断为Int
(或String
),然后检查是否存在隐含的Int
(并且没有隐含的String
)。
同样,隐式解析也会影响类型推断
trait TC[A, B]
implicit val int: TC[Int, String] = null
def foo[A, B](a: A)(implicit tc: TC[A, B]): B = ???
val x = foo(1)
// checking the type
x: String // compiles
这里的类型
String
是从具有唯一实例的类型类中推断出来的。
所以类型推断和隐式解析相互影响但又不同。
<:
和+
属于类型推断,<:<
属于隐式解析。
如果
A <: B
那么A <:< B
def test[A <: B, B] = implicitly[A <:< B] // compiles
但是如果
A <:< B
那么不一定A <: B
def checkSubtype[A <: B, B] = null
def test[A, B](implicit ev: A <:< B) = checkSubtype[A, B] // doesn't compile
<:
由编译器根据规范检查https://scala-lang.org/files/archive/spec/2.13/03-types.html#conformance
<:<
只是一个类型类
sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
只有一个实例
object <:< {
implicit def refl[A]: A =:= A = singleton.asInstanceOf[A =:= A] // the instance
}
sealed abstract class =:=[From, To] extends (From <:< To) with Serializable
所以
<:<
没有很多order的属性。默认情况下没有传递性
def test[A, B, C](implicit ev: A <:< B, ev1: B <:< C) = implicitly[A <:< C] // doesn't compile
没有反对称
def test[A, B](implicit ev: A <:< B, ev1: B <:< A) = implicitly[A =:= B] // doesn't compile
不单调
def test[A, B, F[+_]](implicit ev: A <:< B) = implicitly[F[A] <:< F[B]] // doesn't compile
虽然从 Scala 2.13 开始,标准库中定义了以下方法
sealed abstract class <:<[-From, +To] extends (From => To) with Serializable {
def andThen[C](r: To <:< C): From <:< C = {
type G[-T] = T <:< C
substituteContra[G](r)
}
def liftCo[F[+_]]: F[From] <:< F[To] = {
type G[+T] = F[From] <:< F[T]
substituteCo[G](implicitly[G[From]])
}
}
object <:< {
def antisymm[A, B](implicit l: A <:< B, r: B <:< A): A =:= B = singleton.asInstanceOf[A =:= B]
}
但他们没有定义隐式。因此,如果您需要这些属性,您可以定义传递性
implicit def trans[A, B, C](implicit ev: A <:< B, ev1: B <:< C): A <:< C = ev.andThen(ev1)
def test[A, B, C](implicit ev: A <:< B, ev1: B <:< C) = implicitly[A <:< C] // compiles
反对称更棘手
implicit def antisym[A, B](implicit ev: A <:< B, ev1: B <:< A): (A =:= B) = <:<.antisymm[A, B]
def test[A, B](implicit ev2: A <:< B, ev3: B <:< A) = implicitly[A =:= B] // doesn't compile
如果你手动解析隐式
... = implicitly[A =:= B](antisym[A, B])
,你会看到原因(尽管implicitly[A =:= B](antisym[A, B](ev2, ev3))
有效)
ambiguous implicit values:
both method antisym in object App of type [A, B](implicit ev: A <:< B, ev1: B <:< A): A =:= B
and value ev2 of type A <:< B
match expected type A <:< B
所以你必须解决这种优先隐含的歧义。您不能降低隐式参数
ev2
的优先级。所以你必须降低 antisym
的优先级,这是你在当前范围内的隐式,你不能把它放到隐式范围(伴随对象等)中。我找到的唯一方法是使用shapeless.LowPriority
implicit def antisym[A, B](implicit ev: A <:< B, ev1: B <:< A, lp: LowPriority): (A =:= B) = <:<.antisymm[A, B]
def test[A, B](implicit ev2: A <:< B, ev3: B <:< A) = implicitly[A =:= B] // compiles
同样你可以定义单调性
implicit def liftCo[A, B, F[+_]](implicit ev: A <:< B): F[A] <:< F[B] = ev.liftCo[F]
def test[A, B, F[+_]](implicit ev: A <:< B) = implicitly[F[A] <:< F[B]] // compiles
def test1[A, B](implicit ev: A <:< B) = implicitly[Cov[A] <:< Cov[B]] // compiles
但是如果您将所有实例放入范围内,您将拥有编译时
Stackoverflow
implicit def liftCo[A, B, F[+_]](implicit ev: A <:< B): F[A] <:< F[B] = ev.liftCo[F]
implicit def trans[A, B, C](implicit ev: A <:< B, ev1: B <:< C): A <:< C = ev.andThen(ev1)
implicit def antisym[A, B](implicit ev: A <:< B, ev1: B <:< A, lp: LowPriority): (A =:= B) = <:<.antisymm[A, B]
def test[A, B, F[+_]](implicit ev: A <:< B) = implicitly[F[A] <:< F[B]] // doesn't compile, Stackoverflow
所以我猜你明白为什么默认情况下没有将这些方法定义为隐式方法。这会污染隐式范围。
更多关于
<:
与<:<
的区别https://blog.bruchez.name/posts/generalized-type-constraints-in-scala/
除了(编译时)类型类
<:<
还有(运行时)<:<
来自scala-reflect
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def checkSubtype[A, B]: Unit = macro checkSubtypeImpl[A, B]
def checkSubtypeImpl[A: c.WeakTypeTag, B: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
println(weakTypeOf[A] <:< weakTypeOf[B])
q"()"
}
type A <: B
type B
checkSubtype[A, B] // scalac: true // scalacOptions += "-Ymacro-debug-lite"
type A
type B
checkSubtype[A, B] // scalac: false
Scala 2.13.10.