Посмотрите на следующий метод
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)
В любом случае, scala REPL не упрощает A & Null
до 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 функции, которой обладает TypeScript: сужения типа, зависящего от потока управления. Это очень прискорбно. Я был бы признателен Scala за поддержку в ближайшем будущем. Добавление @unchecked везде утомительно и чревато ошибками.
Это не имеет ничего общего с «сужением типа, зависящего от потока управления». Это просто стирание типа, потому что на самом деле вы выполняете проверку класса, поэтому полный тип теряется во время выполнения.
Я думаю, да. Если бы поддерживалось сужение типа, зависящего от потока управления, то тип value
во втором case
был бы T
и никакого присвоения типа не требовалось.
Справедливо, но это уже другой вопрос. Спецификация языка гласит, что типы объединения должны использоваться посредством проверок классов, поэтому я обычно их избегаю.
Вопросов довольно много, поэтому начну с азов.
Scala основана на исчислении DOT. В Scala каждый тип T
является сегментом типа [Lower, Upper]
. Типы Scala представляют собой решетку (относительно <:
, &
, |
): https://en.wikipedia.org/wiki/Lattice_(order)
T <: AnyRef
не подразумевает T >: Null
, и наоборот.
T <: AnyRef
означает сегмент типа [Nothing, AnyRef]
. T >: Null
означает [Null, Any]
.
(Nothing <: AnyVal <: Any
, Nothing <: Null <: AnyRef <: Any
, Nothing != Null != AnyRef != AnyVal != Any
.)
AnyRef
— это супертип всех ссылочных типов. Null
— подтип всех ссылочных типов. Null
населен только null
.
Nothing
— подтип всех типов (включая Null
: Nothing <: Null
, Nothing != Null
). В нем нет (нормальных) ценностей. Но то, что Nothing
не населен (нормальными) ценностями, не означает, что t: Nothing
вообще ничем не населен. Существуют термины такого типа: T >: Nothing
, есть абстрактные типы/параметры типа, пересекающиеся с этим типом: T <: AnyRef
(и в программировании на уровне типа типы важнее, значения, существуют они или нет, менее важны), нет поэтому нормальные значения (расхождения, нижние значения), такие как
@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
def foo[T <: AnyRef](t: T): Unit = ()
foo[Nothing](infiniteRecursion()) // compiles, runs forever
foo[Nothing](throwException()) // compiles, runs, throws exception
Если 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
и T
(фактически, максимальный такой тип), обычно отличающиеся от Null
и Nat
.
Если определение перекрестка...
- если ценности...
- если ценности...
Думаю, год назад мы уже обсуждали, что типы не являются наборами (значений):
Почему признак, аннотируемый самим собой, не может быть присвоен типу самого себя?
https://math.stackexchange.com/questions/489369/difference-between-a-type-and-a-set
https://planetmath.org/11typetheoryversussettheory
Если два типа имеют равные наборы значений этих типов, это не делает типы равными.
Например, типы ниже Succ[N]
, _0
, _1
, _2
, Nat
, ... являются абстрактными и не имеют значений. Но это совершенно разные типы. Если бы типы были множествами, то все эти типы Succ[N]
, _0
, _1
, _2
, X
, ... были бы одним и тем же множеством (а именно пустым множеством).
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
Согласно теории типов, не существует типа
A & X
такого, чтоX
совпадает сA
для любогоX
.
На самом деле, в системе типов Scala Nothing
есть T
.
> в системе типов Scala X — это Nothing < Если бы это было правдой, то компилятор мог бы упростить A & Nothing до Nothing, но этого не происходит. Если я прав, Nothing — это хак Scala для решения некоторых ситуаций. Но не является реальным типом согласно теории типов. A и B должны иметь все члены A и B, поэтому утверждение «Nothing & A = Nothing для любого A» истинно. Ничто не должно иметь членов всех возможных типов, тем не менее, у него их нет. Или, по крайней мере, компилятор жалуется при доступе к элементу переменной типа Nothing.
@Readren "тогда компилятор мог бы упростить A & Nothing
до Nothing
, но это не так" Конечно, так и есть: scastie.scala-lang.org/DmytroMitin/wLBUOMnTQnSqjAH9f0Os6w/2 "Но это не настоящий тип" Конечно, это настоящий тип. «согласно теории типов» Вы действительно читали это в каком-то учебнике по теории типов? en.wikipedia.org/wiki/Bottom_type «Nothing
должны содержать члены всех возможных типов» Уже обсуждалось: stackoverflow.com/questions/1728541/…
@Readren «только для поддержки исключений». Не только исключения, но и бесконечная рекурсия (во время компиляции не так легко определить, конечна ли рекурсия). Nothing
— это минимальный элемент решетки типов, как Any
— максимальный элемент. Nothing
важен для вывода типа: stackoverflow.com/a/64969830/5249621
>Вы действительно читали это в каком-то учебнике< Эту ложь мне сказал чатGPT. :С
Scastie/metals не показывает мне этот странный тип, но меня согревает проверка времени выполнения: scastie.scala-lang.org/BalmungSan/9vrLo5lGRzuvSNmu1rVagA/4