看下面的方法
def toOption[T <: AnyRef](value: T | Null): Option[T] =
value match {
case null => None
case content: T => Some(content)
}
如果你检查
content
模式变量,类型是T | T & Null
,我猜这是被检查者value: T | Null
的类型和匹配模式element: T
的类型交集的结果。
引起我注意的是 |
运算符的第二个操作数。当 T & Null
将 Null
保留为 T<:AnyRef
的类型时,编译器不应该将 T | Null
部分简化为 content
吗? Null
和 T & Null
类型有什么区别?
如果交集
A & B
的定义是它表示同时属于 A
和 B
的成员的值,则:
T
表示的值包含 null
,则同时属于 T
和 Null
的值集将仅包含 null
值;T
表示的值不包括 null
,则同时属于 T
和 Null
的值集将为空 (Nothing
)。因此,当
T & Null
时,Null | Nothing
= Null
= T <: AnyRef
。
如果交集
A & B
的定义是一个值符合A & B
意味着它同时符合A
和B
,那么:
null
符合 T
那么只有 null
才会同时符合 T
和 Null
;null
不符合 T
,则任何值都不会同时符合 T
和 Null
。因此
T & Null
= Null | Nothing
= Null
当 T <: AnyRef
时。
我遗漏了什么或者编译器有错误吗?
编辑:我忘了提及我正在使用intelliJ进行检查。后来为了确认,我用宏根据编译器显示的是
content
的类型,结果是T
。与 IntelliJ 检查显示的非常不同。
我将 IntelliJ 检查器的错误归咎于编译器。对不起。
这是宏:
inline def typeOf[A](a: A): String = ${typeOfMacro[A]('{a})}
def typeOfMacro[A : Type](a: Expr[A])(using q: Quotes): Expr[String] =
Expr(q.reflect.TypeRepr.of[A].show)
无论如何,当
A & Null
时,scala REPL 不会将 Null
简化为 A <: AnyRef
。
scala> val text: String & Null = null
val text: String & Null = null
所以这个问题仍然存在。
奇怪的是 REPL 也没有将
A & Nothing
简化为 Nothing
。
编辑2:根据类型理论,不存在类型
X
使得对于任何A & X
,X
与A
相同。所以我期望的简化是不正确的。 REPL 给出了正确的类型。所以我的问题的答案是是的,我错过了一些东西。
问题有点多,我就从基础开始吧
Scala 基于 DOT 演算。在 Scala 中,每个类型
T
都是一个类型段 [LowerBound, UpperBound]
。
T <: AnyRef
并不意味着T >: Null
,反之亦然。
T <: AnyRef
表示类型段[Nothing, AnyRef]
。 T >: Null
表示 [Null, Any]
。
AnyRef
是所有引用类型的超类型。 Null
是所有引用类型的子类型。 Null
仅由 null
居住。
Nothing
是所有类型的子类型(包括Null
:Nothing <: Null
、Nothing != Null
)。它没有(正常)值。但没有(正常)值并不意味着 Nothing
根本没有任何东西。有这种类型的术语:t: Nothing
,有与此类型相交的抽象类型/类型参数:T >: Nothing
(并且在类型级编程中,类型更重要,值,无论存在与否,都不那么重要) ,没有像这样正常的值
@tailrec
def infiniteRecursion(): Nothing = infiniteRecursion()
def throwException(): Nothing = throw new RuntimeException("error")
def foo[T <: AnyRef](t: => T): Unit = ()
foo[Nothing](infiniteRecursion()) // compiles, runs, successfully finishes
foo[Nothing](throwException()) // compiles, runs, successfully finishes
如果
T <: AnyRef
隐含 T >: Null
,则 foo[Nothing](infiniteRecursion())
、foo[Nothing](throwException())
将无法编译。
如果您想要同时使用
T <: AnyRef
和 T >: Null
,则只需指定两个类型边界即可:
def foo[T >: Null <: AnyRef] = ...
当
T & Null
时,编译器不应该将Null
部分简化为T <: AnyRef
吗?
不,不应该。应该是
T >: Null
(包括T >: Null <: AnyRef
)。
和Null
类型有什么区别?T & Null
如果
T = Nothing
那么 T & Null = Nothing & Null = Nothing != Null
。如果 T >: Null
那么 T & Null = Null
。如果 T <: Null
(即 T
属于段 [Nothing, Null]
),则 T & Null = T
。如果 T
只是某种类型,那么 T & Null
只是某种类型 <: T
和 <: Null
。
如果交集的定义...
- 如果值...
- 如果值...
类型不是(值)集合。如果两种类型具有相同的这些类型的值集,则这并不意味着类型相等。
例如下面的类型
Nat
、Succ[N]
、_0
、_1
、_2
、... 是抽象的,没有值。但它们是完全不同的类型。如果类型是集合,那么所有类型都将是同一集合(即空集)。
type Nat
type _0 <: Nat
type Succ[N <: Nat] <: Nat
trait Add[N <: Nat, M <: Nat] {
type Out <: Nat
}
object Add {
type Aux[N <: Nat, M <: Nat, Out0 <: Nat] = Add[N, M] { type Out = Out0 }
implicit def zeroAdd[M <: Nat]: Aux[_0, M, M] = null
implicit def succAdd[N <: Nat, M <: Nat](implicit
add: Add[N, M]
): Aux[Succ[N], M, Succ[add.Out]] = null
}
type _1 = Succ[_0]
type _2 = Succ[_1]
type _3 = Succ[_2]
type _4 = Succ[_3]
type _5 = Succ[_4]
implicitly[Add.Aux[_2, _3, _5]] // compiles
trait ToInt[N <: Nat] {
def apply(): Int
}
object ToInt {
implicit val zeroToInt: ToInt[_0] = () => 0
implicit def succToInt[N <: Nat](implicit toInt: ToInt[N]): ToInt[Succ[N]] =
() => toInt() + 1
}
trait AddToInt[N <: Nat, M <: Nat] {
def apply(): Int
}
object AddToInt {
implicit def mkAddToInt[N <: Nat, M <: Nat, Sum <: Nat](implicit
add: Add.Aux[N, M, Sum],
toInt: ToInt[Sum]
): AddToInt[N, M] = () => toInt()
}
def addToInt[N <: Nat, M <: Nat](implicit addToInt: AddToInt[N, M]): Int = addToInt()
addToInt[_2, _3] // 5
根据类型理论,对于任何
,不存在X
与A & X
相同的类型X
。A
其实
X
就是Nothing
。