在 Scala 3 中,是否可以制作协变/逆变类型构造函数以实现强制子类型化?

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

这是一个简单的例子:

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]
的缺失强制转换?为什么它不是默认行为?

scala types implicit upcasting type-coercion
2个回答
0
投票

从技术上讲,

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]
  }
}

0
投票

因为类型推断和隐式解析是不同的。

他们互相影响。事实上,类型推断会影响隐式解析

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.

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